skip to navigation
skip to content

Planet Python

Last update: June 02, 2025 01:42 PM UTC

June 02, 2025


Zato Blog

Enterprise Python: Integrating with Salesforce

Enterprise Python: Integrating with Salesforce

Overview

Salesforce connections are one of the newest additions to Zato 3.2, allowing you to look up and manage Salesforce records and other business data. To showcase it, the article will create a sample Salesforce marketing campaign in a way that does not require the usage of anything else except for basic REST APIs combined with plain Python objects, such as dicts.

If you have not done it already, you can download Zato here.

Basic workflow

The scope of our works will be:

Creating Salesforce credentials

To be able to create as connection to Salesforce in the next step, we need a few credentials. There is a full article about how to prepare them and this section is the gist of it.

In runtime, based on this information, Zato will obtain the necessary authentication and authorization tokens itself, which means that you will only focus on the business side of the integrations, not on the low-level aspects of it.

The process of obtaining the credentials needs to be coordinated with an administrator of your organization. To assist in that, the screenshots below explain where to find them.

The credentials are:

The username and password are simply the same credentials that can be used to log in to Salesforce:

Consumer key and secret are properties of a connected app - this is a term that Salesforce uses for API clients that invoke its services. If you are already an experienced Salesforce REST API user, you may know the key and secret under their aliases of "client_id" and "client_secret" - these are the same objects.

Note that when a connected app already exists and you would like to retrieve the key and secret, they will be available under the "View" menu option for the app, not under "Edit" or "Manage".

Defining a Salesforce connection in Zato

With all the credentials in place, we can create a new Salesforce connection in Zato Dashboard, as below.

Authoring an integration service in Python

Above, we created a connection definition that lets Zato obtain session tokens and establish connections to Salesforce. Now, we can create an API service that will make use of such connections.

In the example below, we are using the POST REST method to invoke an endpoint that creates new Salesforce campaigns. In your own integrations, you can invoke any other Salesforce endpoint, using any REST method as needed, by following the same pattern, which is, create a model with input fields, build a Python dict for the request to Salesforce, invoke it and map all the required from the response from Salesforce to that which your own service returns to its own callers.

Note that we use a datamodel-based SimpleIO definition for the service. Among other things, although we are not going to do it here, this would let us offer definitions for this and other services.

# -*- coding: utf-8 -*-

# stdlib
from dataclasses import dataclass

# Zato
from zato.server.service import Model, Service

# ###########################################################################

if 0:
    from zato.server.connection.salesforce import SalesforceClient

# ###########################################################################

@dataclass(init=False)
class CreateCampaignRequest(Model):
    name:    str
    segment: str

# ###########################################################################

@dataclass(init=False)
class CreateCampaignResponse(Model):
    campaign_id: str

# ###########################################################################

class CreateCampaign(Service):

    class SimpleIO:
        input  = CreateCampaignRequest
        output = CreateCampaignResponse

    def handle(self):

        # This is our input data
        input = self.request.input # type: CreateCampaignRequest

        # Salesforce REST API endpoint to invoke - note that Zato
        # will add a prefix to it containing the API version.
        path = '/sobjects/Campaign/'

        # Build the request to Salesforce based on what we received
        request = {
          'Name': input.name,
          'Segment__c': input.segment,
        }

        # .. create a reference to our connection definition ..
        salesforce = self.cloud.salesforce['My Salesforce Connection']

        # .. obtain a client to Salesforce ..
        with salesforce.conn.client() as client: # type: SalesforceClient

            # .. create the campaign now ..
            sf_response = client.post(path, request)

        # .. build our response object ..
        response = CreateCampaignResponse()
        response.campaign_id = sf_response['id']

        # .. and return its ID to our caller.
        self.response.payload = response

# ###########################################################################

Creating a REST channel

Note that we assign HTTP Basic Auth credentials to the channel. In this manner, it is possible for clients of this REST channel to authenticate using a method that they are already familiar which simplifies everyone's work - it is Zato that deals with how to authenticate against Salesforce whereas your API clients use the ubiquitous HTTP Basic Auth method.

Testing

The last step is to invoke the newly created channel:

$ curl http://api:password@localhost:17010/api/campaign/create -d '{"name":"Hello", "segment":"123"}'
{"campaign_id":"8901Z3VHXDTebEJWs"}
$

That is everything - you have just integrated with Salesforce and exposed a REST channel for external applications to integrate with!

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 Python

June 02, 2025 07:41 AM UTC

June 01, 2025


Zero to Mastery

[May 2025] Python Monthly Newsletter 🐍

66th 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.

June 01, 2025 10:00 AM UTC


Tryton News

Newsletter June 2025

During the last month we focused on fixing bugs and improving the behaviour of things, speeding-up performance issues - building on the changes from our last release 7.6. We also added some new features which we would like to introduce to you in this newsletter.

For an in depth overview of the Tryton issues please take a look at our issue tracker or see the issues and merge requests filtered by label.

Changes for the User

User Interface

Now we store the Message-Id header from sent emails. This change would later allow to manage the status of bouncing, deferred or delivered.

We now export Many2One- and Reference-fields by using their record name. Also we support the export of xxx2Many-fields as a list of names. The import of One2Many-fields now supports also a list of names.

New Releases

We released bug fixes for the currently maintained long term support series
7.0 and 6.0, and for the penultimate series 7.6, 7.4 and 7.2.

Authors: @dave @pokoli @udono

1 post - 1 participant

Read full topic

June 01, 2025 06:00 AM UTC

May 31, 2025


The Python Coding Stack

What's the Difference Between Zipping and Unzipping Your Jacket? • Unzipping in Python

Today's post is short and visual. You probably used Python's zip() before. (But if you haven't, you'll figure out what it does in this article, so don't worry!)

But how often have you used Python's unzipping tool?

Eh?! Did you miss this? Do you want to go and have a look for this tool in your IDE or the Python docs? I'll save you the trouble. You won't find it.

But Python does have an unzipping tool, I promise. Confused? Let's start.


Terminology note: As regular readers may have noticed, I use British English in articles on The Stack, with occasional translations to the US variant when needed. In British English, zip is both the noun and the verb—we don't use the word zipper. However, to differentiate between the clothing zip like the one on your jacket and Python's zip(), I'll use the US English variant zipper to refer to the clothing type.


Zipping • Meet the Team

Meet the team. There are three lists with the names, ages, and locations of four team members in some nondescript team:

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

Let's visualise these lists:

If you had a choice, you might have chosen to use a single data structure to store these related items since the first number in ages is the age of the first person in names, and the first city in locations is where he's located, and so on. But you don't always have a choice. So, let's assume this is what you have, and you need to work with three separate data structures.

In the Python program, there's no connection between elements from the different lists. The only connection is between elements within each list since they're members of the same list.

But you can zip these three lists together. And this works (almost) in the same way as the zipper on your jacket. One difference is that you can zip as many things as you want. The clothing zipper can only zip two sides of the garment.

Let's add zippers to our lists so you can see this better:

Time to zip up:

#2

If I had more time, I'd figure out a way to create an animation for the next phase. But your imagination will have to do instead. Zip up both sets of zippers:

The zipping action brings elements from different lists together. So, "James", 20, and "London" are now connected following the call to zip(). Since zip() returns an iterator, you can use next() to confirm that the iterator’s first element contains these three items:

#3

And why not confirm the rest as well?

#4

Each trio of name, age, and location is grouped within a tuple. Let's visualise the new groupings:

If you try to call next() again, you'll get a StopIteration exception. Read more about this process in this article if you're interested: The Anatomy of a `for` Loop.

If you're following along in the REPL, you'll now need to recreate the zip object team_members since the calls to next() above exhausted the iterator:

#5

Support The Python Coding Stack


Sorting by age

Now, let's sort the team members by age, from youngest to oldest. You can't simply sort the list ages since you also want to rearrange the respective names and locations. You can use the zip object you just created, team_members:

#6

You sort the tuples yielded by the zip object team_members using the built-in sorted(). You use the function's key parameter to ensure you sort using the second element (index = 1) from each tuple. You can read more about sorted() and the key parameter, which you also find in other places in Python, here: The Key To The `key` Parameter in Python.

And if you don't like lambda functions, you can use operator.itemgetter(1), which is also more efficient. But that's taking us off course, so let's move on.

Here's the visual representation of the sorted groupings, which are tuples. They’re ordered based on the person’s age:


Do you want to join a forum to discuss Python further with other Pythonistas? Upgrade to a paid subscription here on The Python Coding Stack to get exclusive access to The Python Coding Place's members' forum. More Python. More discussions. More fun.

Subscribe now

And you'll also be supporting this publication. I put plenty of time and effort into crafting each article. Your support will help me keep this content coming regularly and, importantly, will help keep it free for everyone.


Unzipping

But, you still want to have separate lists for the names, ages, and locations, as this was the original format of your data.

You started off with three separate data structures, names, ages, and locations. Recall how there was no connection within Python between elements of these different lists.

The zip() call solved this problem by linking elements from the three lists together. This allowed you to sort them while ensuring that the names and locations swap places along with the ages.

But now you have a connection between a name, an age, and a location, as each tuple contains one of each. However, you no longer have connections between all the names, all the ages, and all the locations.

So, you need to zip the four tuples together! Recall that zip() can take any number of iterables as arguments. Earlier in this article, you zipped three iterables: the lists names, ages, and locations.

Now, you can zip the four tuples stored in the list sorted_team. Let's add zippers to these four tuples:

You need to zip these together:

Conceptually, you'd like to write something like this:

zip(
    ('Sarah', 19, 'Vancouver'),
    ('James', 20, 'London'),
    ('Kate', 34, 'Sydney'),
    ('Bob', 54, 'San Francisco'),
)

You'd need to pass the four tuples as four positional arguments in zip(). But doing this by hand is too much work. We want to be lazy.

You have a list that contains all these tuples—the list sorted_team. However, you can't simply pass the list to zip() since the list is just one iterable, but zip() needs two or more iterables to zip them together.

Instead, you can unpack the list using the unpacking operator *. Let's return to our REPL session to complete this:

#7

And here's the visual representation:

Note that the built-in sorted() returns a list, which you then unpack within the call to zip() to unzip its contents. You may use other tools in between zipping and unzipping that return iterators or any other iterable. The process remains the same. You'll still need to unpack the final iterable within the call to zip() using the unpacking operator *.

Final Words

So, Python does have an unzipping tool, after all. It's the same tool you use to zip up: zip(). The unzipping process requires the additional step of unpacking the iterable you got from the original zip() and any further processing performed on the data before unzipping.

And that's exactly what you do with your jacket zipper. You use the same zipper to zip up and unzip. You simply reverse the motion.

Image by Tumisu from Pixabay

Image of zipper used in diagrams by Nina Garman from Pixabay


Code in this article uses Python 3.13

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

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

Support The Python Coding Stack


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

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

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

Further reading related to this article’s topic:


Appendix: Code Blocks

Code Block #1
names = ["James", "Bob", "Kate", "Sarah"]
ages = [20, 54, 34, 19]
locations = ["London",  "San Francisco", "Sydney", "Vancouver"]
Code Block #2
team_members = zip(names, ages, locations)
Code Block #3
next(team_members)
# ('James', 20, 'London')
Code Block #4
next(team_members)
# ('Bob', 54, 'San Francisco')
next(team_members)
# ('Kate', 34, 'Sydney')
next(team_members)
# ('Sarah', 19, 'Vancouver')
Code Block #5
team_members = zip(names, ages, locations)
Code Block #6
sorted_team = sorted(team_members, key=lambda grouping: grouping[1])
sorted_team
# [('Sarah', 19, 'Vancouver'), ('James', 20, 'London'), 
#  ('Kate', 34, 'Sydney'), ('Bob', 54, 'San Francisco')]
Code Block #7
names, ages, locations = zip(*sorted_team)

names
# ('Sarah', 'James', 'Kate', 'Bob')
ages
# (19, 20, 34, 54)
locations
# ('Vancouver', 'London', 'Sydney', 'San Francisco')

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

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

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

May 31, 2025 09:47 PM UTC


Anarcat

Traffic meter per ASN without logs

Have you ever found yourself in the situation where you had no or anonymized logs and still wanted to figure out where your traffic was coming from?

Or you have multiple upstreams and are looking to see if you can save fees by getting into peering agreements with some other party?

Or your site is getting heavy load but you can't pinpoint it on a single IP and you suspect some amoral corporation is training their degenerate AI on your content with a bot army?

(You might be getting onto something there.)

If that rings a bell, read on.

TL;DR:

... or just skip the cruft and install asncounter:

pip install asncounter

Also available in Debian 14 or later, or possibly in Debian 13 backports (soon to be released) if people are interested:

apt install asncounter

Then count whoever is hitting your network with:

awk '{print $2}' /var/log/apache2/*access*.log | asncounter

or:

tail -F /var/log/apache2/*access*.log | awk '{print $2}' | asncounter

or:

tcpdump -q -n | asncounter --input-format=tcpdump --repl

or:

tcpdump -q -i eth0 -n -Q in "tcp and tcp[tcpflags] & tcp-syn != 0 and (port 80 or port 443)" | asncounter --input-format=tcpdump --repl

Read on for why this matters, and why I wrote yet another weird tool (almost) from scratch.

Background and manual work

This is a tool I've been dreaming of for a long, long time. Back in 2006, at Koumbit a colleague had setup TAS ("Traffic Accounting System", "Система учета трафика" in Russian, apparently), a collection of Perl script that would do per-IP accounting. It was pretty cool: it would count bytes per IP addresses and, from that, you could do analysis. But the project died, and it was kind of bespoke.

Fast forward twenty years, and I find myself fighting off bots at the Tor Project (the irony...), with our GitLab suffering pretty bad slowdowns (see issue tpo/tpa/team#41677 for the latest public issue, the juicier one is confidential, unfortunately).

(We did have some issues caused by overloads in CI, as we host, after all, a fork of Firefox, which is a massive repository, but the applications team did sustained, awesome work to fix issues on that side, again and again (see tpo/applications/tor-browser#43121 for the latest, and tpo/applications/tor-browser#43121 for some pretty impressive correlation work, I work with really skilled people). But those issues, I believe were fixed.)

So I had the feeling it was our turn to get hammered by the AI bots. But how do we tell? I could tell something was hammering at the costly /commit/ and (especially costly) /blame/ endpoint. So at first, I pulled out the trusted awk, sort | uniq -c | sort -n | tail pipeline I am sure others have worked out before:

awk '{print $1}' /var/log/nginx/*.log | sort | uniq -c | sort -n | tail -10

For people new to this, that pulls the first field out of web server log files, sort the list, counts the number of unique entries, and sorts that so that the most common entries (or IPs) show up first, then show the top 10.

That, other words, answers the question of "which IP address visits this web server the most?" Based on this, I found a couple of IP addresses that looked like Alibaba. I had already addressed an abuse complaint to them (tpo/tpa/team#42152) but never got a response, so I just blocked their entire network blocks, rather violently:

for cidr in 47.240.0.0/14 47.246.0.0/16 47.244.0.0/15 47.235.0.0/16 47.236.0.0/14; do 
  iptables-legacy -I INPUT -s $cidr -j REJECT
done

That made Ali Baba and his forty thieves (specifically their AL-3 network go away, but our load was still high, and I was still seeing various IPs crawling the costly endpoints. And this time, it was hard to tell who they were: you'll notice all the Alibaba IPs are inside the same 47.0.0.0/8 prefix. Although it's not a /8 itself, it's all inside the same prefix, so it's visually easy to pick it apart, especially for a brain like mine who's stared too long at logs flowing by too fast for their own mental health.

What I had then was different, and I was tired of doing the stupid thing I had been doing for decades at this point. I had recently stumbled upon pyasn recently (in January, according to my notes) and somehow found it again, and thought "I bet I could write a quick script that loops over IPs and counts IPs per ASN".

(Obviously, there are lots of other tools out there for that kind of monitoring. Argos, for example, presumably does this, but it's a kind of a huge stack. You can also get into netflows, but there's serious privacy implications with those. There are also lots of per-IP counters like promacct, but that doesn't scale.

Or maybe someone already had solved this problem and I just wasted a week of my life, who knows. Someone will let me know, I hope, either way.)

ASNs and networks

A quick aside, for people not familiar with how the internet works. People that know about ASNs, BGP announcements and so on can skip.

The internet is the network of networks. It's made of multiple networks that talk to each other. The way this works is there is a Border Gateway Protocol (BGP), a relatively simple TCP-based protocol, that the edge routers of those networks used to announce each other what network they manage. Each of those network is called an Autonomous System (AS) and has an AS number (ASN) to uniquely identify it. Just like IP addresses, ASNs are allocated by IANA and local registries, they're pretty cheap and useful if you like running your own routers, get one.

When you have an ASN, you'll use it to, say, announce to your BGP neighbors "I have 198.51.100.0/24" over here and the others might say "okay, and I have 216.90.108.31/19 over here, and I know of this other ASN over there that has 192.0.2.1/24 too! And gradually, those announcements flood the entire network, and you end up with each BGP having a routing table of the global internet, with a map of which network block, or "prefix" is announced by which ASN.

It's how the internet works, and it's a useful thing to know, because it's what, ultimately, makes an organisation responsible for an IP address. There are "looking glass" tools like the one provided by routeviews.org which allow you to effectively run "trace routes" (but not the same as traceroute, which actively sends probes from your location), type an IP address in that form to fiddle with it. You will end up with an "AS path", the way to get from the looking glass to the announced network. But I digress, and that's kind of out of scope.

Point is, internet is made of networks, networks are autonomous systems (AS) and they have numbers (ASNs), and they announced IP prefixes (or "network blocks") that ultimately tells you who is responsible for traffic on the internet.

Introducing asncounter

So my goal was to get from "lots of IP addresses" to "list of ASNs", possibly also the list of prefixes (because why not). Turns out pyasn makes that really easy. I managed to build a prototype in probably less than an hour, just look at the first version, it's 44 lines (sloccount) of Python, and it works, provided you have already downloaded the required datafiles from routeviews.org. (Obviously, the latest version is longer at close to 1000 lines, but it downloads the data files automatically, and has many more features).

The way the first prototype (and later versions too, mostly) worked is that you feed it a list of IP addresses on standard input, it looks up the ASN and prefix associated with the IP, and increments a counter for those, then print the result.

That showed me something like this:

root@gitlab-02:~/anarcat-scripts# tcpdump -q -i eth0 -n -Q in "(udp or tcp)" | ./asncounter.py --tcpdump                                                                                                                                                                          
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode                                                                
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes                                                             
INFO: collecting IPs from stdin, using datfile ipasn_20250523.1600.dat.gz                                                                
INFO: loading datfile /root/.cache/pyasn/ipasn_20250523.1600.dat.gz...                                                                   
INFO: loading /root/.cache/pyasn/asnames.json                       
ASN     count   AS               
136907  7811    HWCLOUDS-AS-AP HUAWEI CLOUDS, HK                                                                                         
[----]  359     [REDACTED]
[----]  313     [REDACTED]
8075    254     MICROSOFT-CORP-MSN-AS-BLOCK, US
[---]   164     [REDACTED]
[----]  136     [REDACTED]
24940   114     HETZNER-AS, DE  
[----]  98      [REDACTED]
14618   82      AMAZON-AES, US                                                                                                           
[----]  79      [REDACTED]
prefix  count                                         
166.108.192.0/20        1294                                                                                                             
188.239.32.0/20 1056                                          
166.108.224.0/20        970                    
111.119.192.0/20        951              
124.243.128.0/18        667                                         
94.74.80.0/20   651                                                 
111.119.224.0/20        622                                         
111.119.240.0/20        566           
111.119.208.0/20        538                                         
[REDACTED]  313           

Even without ratios and a total count (which will come later), it was quite clear that Huawei was doing something big on the server. At that point, it was responsible for a quarter to half of the traffic on our GitLab server or about 5-10 queries per second.

But just looking at the logs, or per IP hit counts, it was really hard to tell. That traffic is really well distributed. If you look more closely at the output above, you'll notice I redacted a couple of entries except major providers, for privacy reasons. But you'll also notice almost nothing is redacted in the prefix list, why? Because all of those networks are Huawei! Their announcements are kind of bonkers: they have hundreds of such prefixes.

Now, clever people in the know will say "of course they do, it's an hyperscaler; just ASN14618 (AMAZON-AES) there is way more announcements, they have 1416 prefixes!" Yes, of course, but they are not generating half of my traffic (at least, not yet). But even then: this also applies to Amazon! This way of counting traffic is way more useful for large scale operations like this, because you group by organisation instead of by server or individual endpoint.

And, ultimately, this is why asncounter matters: it allows you to group your traffic by organisation, the place you can actually negotiate with.

Now, of course, that assumes those are entities you can talk with. I have written to both Alibaba and Huawei, and have yet to receive a response. I assume I never will. In their defence, I wrote in English, perhaps I should have made the effort of translating my message in Chinese, but then again English is the Lingua Franca of the Internet, and I doubt that's actually the issue.

The Huawei and Facebook blocks

Another aside, because this is my blog and I am not looking for a Pullitzer here.

So I blocked Huawei from our GitLab server (and before you tear your shirt open: only our GitLab server, everything else is still accessible to them, including our email server to respond to my complaint). I did so 24h after emailing them, and after examining their user agent (UA) headers. Boy that was fun. In a sample of 268 requests I analyzed, they churned out 246 different UAs.

At first glance, they looked legit, like:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36

Safari on a Mac, so far so good. But when you start digging, you notice some strange things, like here's Safari running on Linux:

Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.457.0 Safari/534.3

Was Safari ported to Linux? I guess that's.. possible?

But here is Safari running on a 15 year old Ubuntu release (10.10):

Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.702.0 Chrome/12.0.702.0 Safari/534.24

Speaking of old, here's Safari again, but this time running on Windows NT 5.1, AKA Windows XP, released 2001, EOL since 2019:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-CA) AppleWebKit/534.13 (KHTML like Gecko) Chrome/9.0.597.98 Safari/534.13

Really?

Here's Firefox 3.6, released 14 years ago, there were quite a lot of those:

Mozilla/5.0 (Windows; U; Windows NT 6.1; lt; rv:1.9.2) Gecko/20100115 Firefox/3.6

I remember running those old Firefox releases, those were the days.

But to me, those look like entirely fake UAs, deliberately rotated to make it look like legitimate traffic.

In comparison, Facebook seemed a bit more legit, in the sense that they don't fake it. most hits are from:

meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)

which, according their documentation:

crawls the web for use cases such as training AI models or improving products by indexing content directly

From what I could tell, it was even respecting our rather liberal robots.txt rules, in that it wasn't crawling the sprawling /blame/ or /commit/ endpoints, explicitly forbidden by robots.txt.

So I've blocked the Facebook bot in robots.txt and, amazingly, it just went away. Good job Facebook, as much as I think you've given the empire to neo-nazis, cause depression and genocide, you know how to run a crawler, thanks.

Huawei was blocked at the web server level, with a friendly 429 status code telling people to contact us (over email) if they need help. And they don't care: they're still hammering the server, from what I can tell, but then again, I didn't block the entire ASN just yet, just the blocks I found crawling the server over a couple hours.

A full asncounter run

So what does a day in asncounter look like? Well, you start with a problem, say you're getting too much traffic and want to see where it's from. First you need to sample it. Typically, you'd do that with tcpdump or tailing a log file:

tail -F /var/log/apache2/*access*.log | awk '{print $2}' | asncounter

If you have lots of traffic or care about your users' privacy, you're not going to log IP addresses, so tcpdump is likely a good option instead:

tcpdump -q -n | asncounter --input-format=tcpdump --repl

If you really get a lot of traffic, you might want to get a subset of that to avoid overwhelming asncounter, it's not fast enough to do multiple gigabit/second, I bet, so here's only incoming SYN IPv4 packets:

tcpdump -q -n -Q in "tcp and tcp[tcpflags] & tcp-syn != 0 and (port 80 or port 443)" | asncounter --input-format=tcpdump --repl

In any case, at this point you're staring at a process, just sitting there. If you passed the --repl or --manhole arguments, you're lucky: you have a Python shell inside the program. Otherwise, send SIGHUP to the thing to have it dump the nice tables out:

pkill -HUP asncounter

Here's an example run:

> awk '{print $2}' /var/log/apache2/*access*.log | asncounter
INFO: using datfile ipasn_20250527.1600.dat.gz
INFO: collecting addresses from <stdin>
INFO: loading datfile /home/anarcat/.cache/pyasn/ipasn_20250527.1600.dat.gz...
INFO: finished reading data
INFO: loading /home/anarcat/.cache/pyasn/asnames.json
count   percent ASN AS
12779   69.33   66496   SAMPLE, CA
3361    18.23   None    None
366 1.99    66497   EXAMPLE, FR
337 1.83    16276   OVH, FR
321 1.74    8075    MICROSOFT-CORP-MSN-AS-BLOCK, US
309 1.68    14061   DIGITALOCEAN-ASN, US
128 0.69    16509   AMAZON-02, US
77  0.42    48090   DMZHOST, GB
56  0.3 136907  HWCLOUDS-AS-AP HUAWEI CLOUDS, HK
53  0.29    17621   CNCGROUP-SH China Unicom Shanghai network, CN
total: 18433
count   percent prefix  ASN AS
12779   69.33   192.0.2.0/24    66496   SAMPLE, CA
3361    18.23   None        
298 1.62    178.128.208.0/20    14061   DIGITALOCEAN-ASN, US
289 1.57    51.222.0.0/16   16276   OVH, FR
272 1.48    2001:DB8::/48   66497   EXAMPLE, FR
235 1.27    172.160.0.0/11  8075    MICROSOFT-CORP-MSN-AS-BLOCK, US
94  0.51    2001:DB8:1::/48 66497   EXAMPLE, FR
72  0.39    47.128.0.0/14   16509   AMAZON-02, US
69  0.37    93.123.109.0/24 48090   DMZHOST, GB
53  0.29    27.115.124.0/24 17621   CNCGROUP-SH China Unicom Shanghai network, CN

Those numbers are actually from my home network, not GitLab. Over there, the battle still rages on, but at least the vampire bots are banging their heads against the solid Nginx wall instead of eating the fragile heart of GitLab. We had a significant improvement in latency thanks to the Facebook and Huawei blocks... Here are the "workhorse request duration stats" for various time ranges, 20h after the block:

range mean max stdev
20h 449ms 958ms 39ms
7d 1.78s 5m 14.9s
30d 2.08s 3.86m 8.86s
6m 901ms 27.3s 2.43s

We went from two seconds mean to 500ms! And look at that standard deviation! 39ms! It was ten seconds before! I doubt we'll keep it that way very long but for now, it feels like I won a battle, and I didn't even have to setup anubis or go-away, although I suspect that will unfortunately come.

Note that asncounter also supports exporting Prometheus metrics, but you should be careful with this, as it can lead to cardinal explosion, especially if you track by prefix (which can be disabled with --no-prefixes`.

Folks interested in more details should read the fine manual for more examples, usage, and discussion. It shows, among other things, how to effectively block lots of networks from Nginx, aggregate multiple prefixes, block entire ASNs, and more!

So there you have it: I now have the tool I wish I had 20 years ago. Hopefully it will stay useful for another 20 years, although I'm not sure we'll have still have internet in 20 years.

I welcome constructive feedback, "oh no you rewrote X", Grafana dashboards, bug reports, pull requests, and "hell yeah" comments. Hacker News, let it rip, I know you can give me another juicy quote for my blog.

This work was done as part of my paid work for the Tor Project, currently in a fundraising drive, give us money if you like what you read.

May 31, 2025 02:32 AM UTC

May 30, 2025


Real Python

The Real Python Podcast – Episode #251: Python Thread Safety & Managing Projects With uv

What are the ways you can manage multithreaded code in Python? What synchronization techniques are available within Python's threading module? Christopher Trudeau is back on the show this week, bringing another batch of PyCoder's Weekly articles and projects.


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

May 30, 2025 12:00 PM UTC


Kay Hayen

Nuitka Release 2.7

This is to inform you about the new stable release of Nuitka. It is the extremely compatible Python compiler, “download now”.

This release adds a ton of new features and corrections.

Bug Fixes

Package Support

New Features

Optimization

Anti-Bloat

Organizational

Tests

Cleanups

Summary

This release was supposed to focus on scalability, but that didn’t happen due to a variety of important issues coming up as well as unplanned private difficulties.

The added DLL mode will be very interesting to many users, but needs more polish in future releases.

For compatibility, working with the popular (yet - not yes recommended UV-Python), Windows UI fixes for temporary onefile and macOS improvements, as well as improved Android support are excellent.

The next release of Nuitka however will have to focus on scalability and maintenance only. But as usual, not sure if it can happen.

May 30, 2025 05:02 AM UTC

May 29, 2025


Seth Michael Larson

Volunteer Responsibility Amnesty Day (Spring 2025)

After thinking about doing so for the past few years I am finally following through on observing Volunteer Responsibility Amnesty Day (abbreviated VRAD), specifically for most of my volunteer open source contributions. I am posting this ahead of the actual day (June 21st in 2025) to give you all reading an opportunity to compile your own list, if necessary.

I've been trying to diversify my sources of dopamine for some time now and while I've done a decent job of picking up new things I have done a "less-than-good" job of explicitly putting down other things. So now it's time to do that instead of pretending I can do it all.

This isn't a sudden change. I've been lucky to work at two places that supported aspects of open source maintenance, both at Elastic and the Python Software Foundation. It has been difficult after a full day of staring at a screen with the GitHub UI to convince myself to do that for just a few more hours in my free time.

Recently I removed both GitHub and Slack from my phone, and it felt really, really, good -- like this change was already overdue.

I made a list of projects I consider myself a "maintainer" like VRAD suggests. Here are the projects I am no longer maintaining in a volunteer capacity:

Here are the projects that I consider feature-complete and will only be making security-fix releases as necessary going forward:

And here are the projects that I plan to continue to maintain beyond only security releases:

I wanted to highlight that publishing this post, even when I haven't contributed meaningfully to some projects in this list in over a year, was still a difficult thing to do. Those flames won't suddenly come back "if only I pretend just a bit longer" and this post is part of making peace with that.

I still love open source software, especially working on security at the Python Software Foundation and all the friends I've met along the way. Big thank you to my friend Sumana for creating Volunteer Responsibility Amnesty Day.

May 29, 2025 12:00 AM UTC

May 28, 2025


Real Python

Control Flow Structures in Python

Python’s control flow structures allow you to dictate the order in which statements execute in your program. You can do this by using structures like conditionals, loops, and others.

Normally, your code executes sequentially. You can modify this behavior using control flow structures that let you make decisions, run specific pieces of code in response to certain conditions, repeat a code block several times, and more.

Knowing about control flow structures is a fundamental skill for you as a Python developer because they’ll allow you to fine-tune how your programs behave.

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

  • Control flow in Python refers to the order in which code statements are executed or evaluated.
  • Common control flow statements in Python include conditionals with the if, elif, else keywords, loops with for and while, exception handling with tryexcept, and structural pattern matching with matchcase.
  • Control flow structures in Python let you make decisions, repeat tasks, and handle exceptions, enhancing the dynamism and robustness of your code.

To dive deeper into Python’s control flow, explore how these constructs allow you to write more dynamic and flexible programs by making decisions and handling repetitive tasks efficiently.

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

Take the Quiz: Test your knowledge with our interactive “Control Flow Structures in Python” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Control Flow Structures in Python

In this quiz, you'll test your understanding of Python control flow structures, which include conditionals, loops, exception handling, and structural pattern matching. Strengthening these skills will help you write more dynamic, smart, and robust Python code.

Getting to Know Control Flow in Python

Most programming languages, including Python, execute code sequentially from the top of the source file to the bottom, line by line. This way of running code is entirely logical. It’s like following a series of steps in order. However, what if you’re solving a problem with two or more action paths that depend on the result of evaluating a given condition?

For example, say that you’re building an online store and need to implement a feature that decides whether a customer is eligible for free shipping. You’ve decided that if the order is greater than $150.00, then the customer gets free shipping. In this situation, you have two action paths:

  1. If the order is less than $150.00, then the customer doesn’t get free shipping.
  2. If the order is equal to or greater than $150.00, then the customer gets free shipping.

Now, think of a way you could do this with sequential statements. It isn’t an easy task, right? You’d need something that allows you to check the order and decide what course of action to take. That’s exactly what a conditional statement lets you do:

Python
>>> order_total = 215.00

>>> if order_total >= 150:
...     print("You got free shipping!")
... else:
...     print("The shipping fee is $5.00")
...
You got free shipping!
Copied!

Note how the code isn’t executed sequentially. Instead, the execution path depends on the condition’s result. Statements and syntax constructs that allow you to alter the normal execution flow as you did in the example above are known as control flow structures.

Note: In programming, the ability of a program to choose between multiple paths of execution based on certain conditions is known as branching.

In programming, the term control flow refers to the order in which individual statements are executed or evaluated within a program. As you already know, the normal flow of execution is sequential. However, you can alter this by using control flow statements, which include conditionals, loops, and several others.

Here’s another example. This time, you need to repeat a task several times. You can do this by duplicating the same line of code as many times as needed:

Python greeting.py
print("Hello!")
print("Hello!")
print("Hello!")
Copied!

This code works. However, repeating the same code several times is error-prone and introduces maintainability issues. Additionally, what if you don’t know the number of repetitions beforehand? In this situation, a loop will save you:

Python
>>> for _ in range(3):
...     print("Hello!")
...
Hello!
Hello!
Hello!
Copied!

In this example, you use a for loop to run the code three times. This code is much more elegant, flexible, and less repetitive.

Control flow statements like these let you make decisions, repeat tasks, and handle exceptions, making your code more dynamic and powerful. In short, they let you customize the control flow of your programs. In the rest of this tutorial, you’ll dive into Python’s most commonly used control flow statements.

Using Conditional Statements

You took a quick peek at conditional statements in the previous section. A conditional statement is a syntax construct that lets you execute certain code blocks only when a specific condition is true, while skipping them when the condition is false. It allows your programs to respond to different situations rather than just running sequentially.

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


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

May 28, 2025 02:00 PM UTC

Quiz: Control Flow Structures in Python

In this quiz, you’ll test your understanding of control flow structures in Python. Control flow dictates the order in which your code executes, letting you make choices, repeat work, and handle exceptions to build more flexible, reliable programs. For hands-on examples and a deeper dive, check out the tutorial Control Flow Structures in Python.


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

May 28, 2025 12:00 PM UTC


Wingware

Wing Python IDE Version 10.0.10 - May 28, 2025

Wing 10.0.10 fixes the Python Shell and Debug Probe when running Python 3.13.3 or later.

See the change log for details.

Download Wing 10 Now: Wing Pro | Wing Personal | Wing 101 | Compare Products


What's New in Wing 10


Wing 10 Screen Shot

AI Assisted Development

Wing Pro 10 takes advantage of recent advances in the capabilities of generative AI to provide powerful AI assisted development, including AI code suggestion, AI driven code refactoring, description-driven development, and AI chat. You can ask Wing to use AI to (1) implement missing code at the current input position, (2) refactor, enhance, or extend existing code by describing the changes that you want to make, (3) write new code from a description of its functionality and design, or (4) chat in order to work through understanding and making changes to code.

Examples of requests you can make include:

"Add a docstring to this method"
"Create unit tests for class SearchEngine"
"Add a phone number field to the Person class"
"Clean up this code"
"Convert this into a Python generator"
"Create an RPC server that exposes all the public methods in class BuildingManager"
"Change this method to wait asynchronously for data and return the result with a callback"
"Rewrite this threaded code to instead run asynchronously"

Yes, really!

Your role changes to one of directing an intelligent assistant capable of completing a wide range of programming tasks in relatively short periods of time. Instead of typing out code by hand every step of the way, you are essentially directing someone else to work through the details of manageable steps in the software development process.

Read More

Support for Python 3.12, 3.13, and ARM64 Linux

Wing 10 adds support for Python 3.12 and 3.13, including (1) faster debugging with PEP 669 low impact monitoring API, (2) PEP 695 parameterized classes, functions and methods, (3) PEP 695 type statements, and (4) PEP 701 style f-strings.

Wing 10 also adds support for running Wing on ARM64 Linux systems.

Poetry Package Management

Wing Pro 10 adds support for Poetry package management in the New Project dialog and the Packages tool in the Tools menu. Poetry is an easy-to-use cross-platform dependency and package manager for Python, similar to pipenv.

Ruff Code Warnings & Reformatting

Wing Pro 10 adds support for Ruff as an external code checker in the Code Warnings tool, accessed from the Tools menu. Ruff can also be used as a code reformatter in the Source > Reformatting menu group. Ruff is an incredibly fast Python code checker that can replace or supplement flake8, pylint, pep8, and mypy.


Try Wing 10 Now!


Wing 10 is a ground-breaking new release in Wingware's Python IDE product line. Find out how Wing 10 can turbocharge your Python development by trying it today.

Downloads: Wing Pro | Wing Personal | Wing 101 | Compare Products

See Upgrading for details on upgrading from Wing 9 and earlier, and Migrating from Older Versions for a list of compatibility notes.

May 28, 2025 01:00 AM UTC

May 27, 2025


PyCoder’s Weekly

Issue #683: Narwhals, ty, LangChain, and More (May 27, 2025)

#683 – MAY 27, 2025
View in Browser »

The PyCoder’s Weekly Logo


Narwhals: Unified DataFrame Functions

Narwhals is a lightweight compatibility layer between DataFrame libraries. You can use it as a common interface to write reproducible and maintainable data science code which supports pandas, Polars, DuckDB, PySpark, PyArrow, and more
CODECUT.AI • Shared by Marco Gorelli

ty: Astral’s New Type Checker (Formerly Red-Knot)

The folks over at Astral are back with another amazing project: ty, formerly known as Red-Knot, it is a new type checker. Talk Python interviews Charlie Marsh and Carl Meyer about this new tool.
KENNEDY ET AL podcast

First Steps With LangChain

Large language models (LLMs) have taken the world by storm. In this step-by-step video course, you’ll learn to use the LangChain library to build LLM-assisted applications.
REAL PYTHON course

Python Jobs

Sr. Software Developer (Python, Healthcare) (USA)

Prenosis

Senior Software Engineer – Quant Investment Platform (LA or Dallas) (Los Angeles, CA, USA)

Causeway Capital Management LLC

More Python Jobs >>>

Articles & Tutorials

Understanding Random Forest Using Python

A Random Forest is a powerful machine learning algorithm that can be used for classification and regression, is interpretable, and doesn’t require feature scaling. Here’s how to apply it with scikit-learn.
MICHAEL GALARNYK • Shared by Michael Galarnyk

Understanding Python Web Deployment

Trying to deploy server-side Python web applications may be overwhelming. This tutorial breaks down the why of things, rather than the what, and makes recommendations on how to approach the problem.
MIREK DŁUGOSZ

Meta Announces Pyrefly

Pyrefly is an open souce Python type checker and IDE extension built in Rust. This post announces its availability, tells you why they built it, and how you can use it.
META

How to Group Data Using Polars .group_by()

Start using Polars .group_by() to make sense of your data. This tutorial shows you how to group, aggregate, and reveal hidden insights with hands-on examples.
REAL PYTHON

Quiz: How to Group Data Using Polars .group_by()

REAL PYTHON

Nested Loops in Python

Learn how to use nested loops in Python to iterate over multiple sequences and perform repeated actions efficiently in your programs.
REAL PYTHON

Quiz: Nested Loops in Python

REAL PYTHON

Machine Learning With DuckDB and scikit-learn

Learn how to prototype a machine learning workflow using DuckDB for data handling and scikit-learn for modeling.
PETRICA LEUCA

Python: The Documentary

There’s a new documentary in the works on Python and its popularity. This is the official trailer.
YOUTUBE.COM video

The Guide to Hashing I Wish I Had When I Started

Learn the basics of hashing in this beginner-friendly guide. Discover what hashing is, how it works, its key principles, common algorithms, and practical uses for password storage and file integrity. (Code examples aren’t in Python, but most of the explanation is in text).
ANTON ÖDMAN

Loading Pydantic Models From JSON

Pydantic’s JSON loading uses a huge amount of memory; here’s how to reduce it.
ITAMAR TURNER-TRAURING

Projects & Code

flowshow: Wrapper for Python Tasks That Form a Flow

GITHUB.COM/KOANING

sql-tstring: t-string Construction of SQL Queries

GITHUB.COM/PGJONES

strif: String, File, and Object Utilities

GITHUB.COM/JLEVY

pipask: Safer Installs With Audit and Consent 𝘣𝘦𝘧𝘰𝘳𝘦 Install

GITHUB.COM/FEYNMANIX

VectorVFS: Your Filesystem as a Vector Database

VECTORVFS.READTHEDOCS.IO

Events

Weekly Real Python Office Hours Q&A (Virtual)

May 28, 2025
REALPYTHON.COM

PyCon Italia 2025

May 28 to June 1, 2025
PYCON.IT

PyDelhi User Group Meetup

May 31, 2025
MEETUP.COM

PythOnRio Meetup

May 31, 2025
PYTHON.ORG.BR

Django Girls Medellín

June 1 to June 2, 2025
DJANGOGIRLS.ORG

Melbourne Python Users Group, Australia

June 2, 2025
J.MP

AfroPython Conf 2025

June 7 to June 8, 2025
AFROPYTHONCONF.ORG


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

May 27, 2025 07:30 PM UTC


Real Python

The LEGB Rule & Understanding Python Scope

The concept of scope rules how variables and names are looked up in your code. It determines the visibility of a variable within the code. The scope of a name or variable depends on the place in your code where you create that variable. The Python scope concept is generally presented using a rule known as the LEGB rule.

The letters in the acronym LEGB stand for Local, Enclosing, Global, and Built-in scopes. This summarizes not only the Python scope levels but also the sequence of steps that Python follows when resolving names in a program.

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 ]

May 27, 2025 02:00 PM UTC


Python Morsels

Looping in reverse

Any reversible iterable can be reversed using the built-in reversed function whereas Python's slicing syntax only works on sequences.

Table of contents

  1. Reversing sequences with slicing
  2. The list reverse method
  3. Python's reversed function
  4. Reversible iterables
  5. reversed returns an iterator
  6. reversed is a looping helper
  7. Use Python's reversed function to loop in reverse

Reversing sequences with slicing

If you're working with a list, a string, or any other sequence in Python, you can reverse that sequence using Python's slicing syntax:

>>> colors = ["purple", "blue", "green", "pink", "red"]
>>> colors[::-1]
['red', 'pink', 'green', 'blue', 'purple']

The syntax looks weird, but it does work.

If we wanted to write a for loop that iterated over our list from the end to the beginning, we could loop over the reversed slice of that list:

>>> for color in colors[::-1]:
...     print("I like", color)
...
I like red
I like pink
I like green
I like blue
I like purple

But this only works for sequences (meaning iterables that can be indexed).

Also, it looks a little bit weird.

The list reverse method

What about the reverse method?

Read the full article: https://www.pythonmorsels.com/looping-in-reverse/

May 27, 2025 03:00 AM UTC

May 26, 2025


Python Insider

Python 3.14.0 beta 2 is here!

Here’s the second 3.14 beta.

https://www.python.org/downloads/release/python-3140b2/

This is a beta preview of Python 3.14

Python 3.14 is still in development. This release, 3.14.0b2, is the second of four planned beta releases.

Beta release previews are intended to give the wider community the opportunity to test new features and bug fixes and to prepare their projects to support the new feature release.

We strongly encourage maintainers of third-party Python projects to test with 3.14 during the beta phase and report issues found to the Python bug tracker as soon as possible. While the release is planned to be feature-complete entering the beta phase, it is possible that features may be modified or, in rare cases, deleted up until the start of the release candidate phase (Tuesday 2025-07-22). Our goal is to have no ABI changes after beta 4 and as few code changes as possible after the first release candidate. To achieve that, it will be extremely important to get as much exposure for 3.14 as possible during the beta phase.

This includes creating pre-release wheels for 3.14, as it helps other projects to do their own testing. However, we recommend that your regular production releases wait until 3.14.0rc1, to avoid the risk of ABI breaks.

Please keep in mind that this is a preview release and its use is not recommended for production environments.

Major new features of the 3.14 series, compared to 3.13

Some of the major new features and changes in Python 3.14 are:

New features

(Hey, fellow core developer, if a feature you find important is missing from this list, let Hugo know.)

For more details on the changes to Python 3.14, see What’s new in Python 3.14. The next pre-release of Python 3.14 will be 3.14.0b3, scheduled for 2025-06-17.

Build changes

Incompatible changes, removals and new deprecations

Python install manager

The installer we offer for Windows is being replaced by our new install manager, which can be installed from the Windows Store or our FTP page. See our documentation for more information. The JSON file available for download below contains the list of all the installable packages available as part of this release, including file URLs and hashes, but is not required to install the latest release. The traditional installer will remain available throughout the 3.14 and 3.15 releases.

More resources

And now for something completely different

In 1897, the State of Indiana almost passed a bill defining π as 3.2.

Of course, it’s not that simple.

Edwin J. Goodwin, M.D., claimed to have come up with a solution to an ancient geometrical problem called squaring the circle, first proposed in Greek mathematics. It involves trying to draw a circle and a square with the same area, using only a compass and a straight edge. It turns out to be impossible because π is transcendental (and this had been proved just 13 years earlier by Ferdinand von Lindemann), but Goodwin fudged things so the value of π was 3.2 (his writings have included at least nine different values of π: including 4, 3.236, 3.232, 3.2325… and even 9.2376…).

Goodwin had copyrighted his proof and offered it to the State of Indiana to use in their educational textbooks without paying royalties, provided they endorsed it. And so Indiana Bill No. 246 was introduced to the House on 18th January 1897. It was not understood and initially referred to the House Committee on Canals, also called the Committee on Swamp Lands. They then referred it to the Committee on Education, who duly recommended on 2nd February that “said bill do pass”. It passed its second reading on the 5th and the education chair moved that they suspend the constitutional rule that required bills to be read on three separate days. This passed 72-0, and the bill itself passed 67-0.

The bill was referred to the Senate on 10th February, had its first reading on the 11th, and was referred to the Committee on Temperance, whose chair on the 12th recommended “that said bill do pass”.

A mathematics professor, Clarence Abiathar Waldo, happened to be in the State Capitol on the day the House passed the bill and walked in during the debate to hear an ex-teacher argue:

The case is perfectly simple. If we pass this bill which establishes a new and correct value for pi , the author offers to our state without cost the use of his discovery and its free publication in our school text books, while everyone else must pay him a royalty.

Waldo ensured the senators were “properly coached”; and on the 12th, during the second reading, after an unsuccessful attempt to amend the bill it was postponed indefinitely. But not before the senators had some fun.

The Indiana News reported on the 13th:

…the bill was brought up and made fun of. The Senators made bad puns about it, ridiculed it and laughed over it. The fun lasted half an hour. Senator Hubbell said that it was not meet for the Senate, which was costing the State $250 a day, to waste its time in such frivolity. He said that in reading the leading newspapers of Chicago and the East, he found that the Indiana State Legislature had laid itself open to ridicule by the action already taken on the bill. He thought consideration of such a proposition was not dignified or worthy of the Senate. He moved the indefinite postponement of the bill, and the motion carried.

Enjoy the new release

Thanks to all of the many volunteers who help make Python Development and these releases possible! Please consider supporting our efforts by volunteering yourself or through organisation contributions to the Python Software Foundation.

Regards from Helsinki, still light at 10pm,

Your release team,
Hugo van Kemenade
Ned Deily
Steve Dower
Łukasz Langa

May 26, 2025 03:40 PM UTC


Real Python

Marimo: A Reactive, Reproducible Notebook

Marimo notebooks redefine the notebook experience by offering a reactive environment that addresses the limitations of traditional linear notebooks. With marimo, you can seamlessly reproduce and share content while benefiting from automatic cell updates and a correct execution order. Discover how marimo’s features make it an ideal tool for documenting research and learning activities.

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

  • Marimo notebooks automatically update dependent cells, ensuring consistent results across your work.
  • Reactivity allows marimo to determine the correct running order of cells using a directed acyclic graph (DAG).
  • Sandboxing in marimo creates isolated environments for notebooks, preventing package conflicts and ensuring reproducibility.
  • You can add interactivity to marimo notebooks using UI elements like sliders and radio buttons.
  • Traditional linear notebooks have inherent flaws, such as hidden state issues, that marimo addresses with its reactive design.

Before you can get started with marimo, you’ll need to install it. Fortunately, this is quick and easy to do:

Shell
$ python -m pip install marimo
Copied!

You use pip to install the marimo library on your system. With this done, it’s time to get started, be amazed, and learn all about a different type of notebook.

The best way to approach this tutorial is to use the instructions to complete the various examples and try the exercises yourself. If you want copies of the various notebook files created during the tutorial, you’ll find them in your download bundle. The README.md file provides further details of what’s in your downloads.

Take the Quiz: Test your knowledge with our interactive “Marimo: A Reactive, Reproducible Notebook” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Marimo: A Reactive, Reproducible Notebook

This quiz is a great way to reinforce and build on what you've learned about marimo notebooks. You'll find most of the answers in the tutorial, but you'll need to do some additional research to find some of the answers.

How to Get Started in a Marimo Notebook

A notebook is a file where you can write your programming code, run it, and view the output. You can add formatted text to explain how your code works, display charts to clarify results, and even allow your notebook’s users to try out different scenarios using a customized user interface. Once your notebook is complete, you can save everything in a single file and share your creation with others.

In this section, you’ll learn to use marimo to create a simple notebook to perform a calculation and clearly display its results.

Like many notebooks, marimo notebooks consist of cells. The primary cell types are code cells, where you enter and run your Python code, and Markdown cells, where you enter formatted text to augment the code and its output.

In this first example, you’ll use a marimo notebook and NumPy to solve a pair of simultaneous equations. To do this, you’ll first need to install the NumPy library:

Shell
$ python -m pip install numpy
Copied!

With NumPy installed, you can now create your notebook by typing the following command into your console:

Shell
$ marimo edit simultaneous_equations.py
Copied!

When you run this command, you’ll most likely create a new notebook named simultaneous_equations.py for editing. If you already have a marimo notebook with that name, you’ll open it instead. Either way, your notebook will be ready for you to use within your browser.

Switch to your web browser, and you’ll see your new notebook. It’ll contain a single cell. Hover your mouse over the cell to reveal a range of icons, each with a tooltip that explains its purpose and shows the associated keyboard shortcuts:

graphic showing a marimo notebook cell and its controls.

Each of the main icons are described in the screenshot above. While most of these are self-explanatory, there are some points you should be aware of:

  • The red trash can icon shown here won’t appear immediately in your notebook. This is used to delete a cell and will only appear when you add other cells. You can’t see it yet because all notebooks must have at least one cell. Deleting the last cell is impossible.
  • The color of the Run current cell icon is also significant. If this cell is white, as it is in the screenshot, it’s up to date and doesn’t need to be run. Once you start changing cells, you’ll see their Run icons develop a yellow tinge. This warns you that the cell has become stale, meaning you must run it to update it.
  • Finally, notice that the numbers to the left of each cell indicate the line numbers of the code within the cell. Unlike most other notebooks, there are no numbers to indicate the running order of the cells. This is because marimo allows you to add code cells in any order. Marimo can work out the correct cell running order for itself. Even so, placing cells in an illogical order should be avoided.

When you hover your mouse over some of marimo’s icons, you’ll see their associated keyboard shortcuts. Unfortunately, they don’t work correctly in all browsers. If they don’t work for you, stick to using your mouse. Feel free to try them to find out if they work for you.

Adding Code and Markdown Content

It’s time for you to gain experience creating some content in marimo. By following the walk-through, you’ll get hands-on practice with the basics.

Although confusing the first time you see it, the single cell that contains import marimo as mo is actually a blank cell. This code allows you to work with the marimo API. However, it’s not in the cell unless you type it in manually.

Read the full article at https://realpython.com/marimo-notebook/ »


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

May 26, 2025 02:00 PM UTC

Quiz: Marimo: A Reactive, Reproducible Notebook

Why not challenge yourself and see how much you know about marimo notebooks?

Working your way through this quiz is a great way to reinforce and build on what you learned in the Marimo: A Reactive, Reproducible Notebook tutorial.

You could try answering the questions without reading the tutorial first, but you’d miss out on a great learning experience!


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

May 26, 2025 12:00 PM UTC


Python Bytes

#433 Dev in the Arena

<strong>Topics covered in this episode:</strong><br> <ul> <li><a href="https://github.com/k88hudson/git-flight-rules?featured_on=pythonbytes"><strong>git-flight-rules</strong></a></li> <li><a href="https://snarky.ca/unravelling-t-strings/?featured_on=pythonbytes"><strong>Uravelling t-strings</strong></a></li> <li><a href="https://github.com/Abdenasser/neohtop?featured_on=pythonbytes"><strong>neohtop</strong></a></li> <li><strong><a href="https://engineering.fb.com/2025/05/15/developer-tools/introducing-pyrefly-a-new-type-checker-and-ide-experience-for-python/?featured_on=pythonbytes">Introducing Pyrefly: A new type checker and IDE experience for Python</a></strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=gFiSkcu4kU4' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="433">Watch on YouTube</a><br> <p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python Training</strong></a></li> <li><a href="https://courses.pythontest.com/p/the-complete-pytest-course?featured_on=pythonbytes"><strong>The Complete pytest Course</strong></a></li> <li><a href="https://www.patreon.com/pythonbytes"><strong>Patreon Supporters</strong></a></li> </ul> <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>Michael #1:</strong> <a href="https://github.com/k88hudson/git-flight-rules?featured_on=pythonbytes"><strong>git-flight-rules</strong></a></p> <ul> <li>What are "flight rules"? <ul> <li>A guide for astronauts (now, programmers using Git) about what to do when things go wrong.</li> <li>Flight Rules are the hard-earned body of knowledge recorded in manuals that list, step-by-step, what to do if X occurs, and why. Essentially, they are extremely detailed, scenario-specific standard operating procedures. [...]</li> <li>NASA has been capturing our missteps, disasters and solutions since the early 1960s, when Mercury-era ground teams first started gathering "lessons learned" into a compendium that now lists thousands of problematic situations, from engine failure to busted hatch handles to computer glitches, and their solutions.</li> </ul></li> <li>Steps for common operations and actions <ul> <li><a href="https://github.com/k88hudson/git-flight-rules?tab=readme-ov-file#i-want-to-start-a-local-repository">I want to start a local repository</a></li> <li><a href="https://github.com/k88hudson/git-flight-rules?tab=readme-ov-file#what-did-i-just-commit">What did I just commit?</a></li> <li><a href="https://github.com/k88hudson/git-flight-rules?tab=readme-ov-file#i-want-to-discard-specific-unstaged-changes">I want to discard specific unstaged changes</a></li> <li><a href="https://github.com/k88hudson/git-flight-rules?tab=readme-ov-file#restore-a-deleted-file">Restore a deleted file</a></li> </ul></li> </ul> <p><strong>Brian #2:</strong> <a href="https://snarky.ca/unravelling-t-strings/?featured_on=pythonbytes"><strong>Uravelling t-strings</strong></a></p> <ul> <li>Brett Cannon</li> <li>Article walks through <ul> <li>Evaluating the Python expression</li> <li>Applying specified conversions</li> <li>Applying format specs</li> <li>Using an Interpolation class to hold details of replacement fields</li> <li>Using Template class to hold parsed data</li> </ul></li> <li>Plus, you don’t have to have Python 3.14.0b1 to try this out.</li> <li>The end result is very close to an <a href="https://peps.python.org/pep-0750/#example-implementing-f-strings-with-t-strings">example used in PEP 750</a>, which you do need 3.14.0b1 to try out.</li> <li>See also: <ul> <li>I’ve written a pytest version, <a href="https://pythontest.com/unravelling-t-strings-pytest/?featured_on=pythonbytes">Unravelling t-strings with pytest</a>, if you want to run all the examples with one file.</li> </ul></li> </ul> <p><strong>Michael #3:</strong> <a href="https://github.com/Abdenasser/neohtop?featured_on=pythonbytes"><strong>neohtop</strong></a></p> <ul> <li>Blazing-fast system monitoring for your desktop</li> <li>Features <ul> <li>Real-time process monitoring</li> <li>CPU and Memory usage tracking</li> <li>Beautiful, modern UI with dark/light themes</li> <li>Advanced process search and filtering</li> <li>Pin important processes</li> <li>Process management (kill processes)</li> <li>Sort by any column</li> <li>Auto-refresh system stats</li> </ul></li> </ul> <p><img src="https://blobs.pythonbytes.fm/neohop.png" alt="" /></p> <p><strong>Brian #4:</strong> <a href="https://engineering.fb.com/2025/05/15/developer-tools/introducing-pyrefly-a-new-type-checker-and-ide-experience-for-python/?featured_on=pythonbytes">Introducing Pyrefly: A new type checker and IDE experience for Python</a></p> <ul> <li>From Facebook / Meta</li> <li>Another Python type checker written in Rust</li> <li>Built with IDE integration in mind from the beginning</li> <li>Principles <ul> <li>Performance</li> <li>IDE first</li> <li>Inference (inferring types in untyped code)</li> <li>Open source</li> </ul></li> <li>I mistakenly tried this on the project I support with the most horrible abuses of the dynamic nature of Python, <a href="https://github.com/okken/pytest-check?featured_on=pythonbytes">pytest-check</a>. It didn’t go well. But perhaps the project is ready for some refactoring. I’d like to try it soon on a more well behaved project.</li> </ul> <p><strong>Extras</strong> </p> <p>Brian:</p> <ul> <li><a href="https://www.youtube.com/watch?v=pqBqdNIPrbo">Python: The Documentary Official Trailer</a></li> <li>Tim Hopper added <a href="https://pydevtools.com/handbook/tutorial/setting-up-testing-with-pytest-and-uv/?featured_on=pythonbytes">Setting up testing with ptyest and uv</a> to his “Python Developer Tooling Handbook”</li> <li>For a more thorough intro on pytest, check out <a href="https://courses.pythontest.com?featured_on=pythonbytes">courses.pythontest.com</a></li> <li><a href="https://getpocket.com/farewell?featured_on=pythonbytes">pocket is closing</a>, I’m switching to <a href="https://raindrop.io?featured_on=pythonbytes">Raindrop</a> <ul> <li>I got one question about code formatting. It’s not highlighted, but otherwise not bad.</li> </ul></li> </ul> <p>Michael:</p> <ul> <li>New course! <a href="https://training.talkpython.fm/courses/polars-for-power-users?featured_on=pythonbytes">Polars for Power Users: Transform Your Data Analysis Game</a></li> <li><a href="https://github.com/apache/airflow/releases/tag/3.0.0?featured_on=pythonbytes">Apache Airflow 3.0 Released</a></li> <li><a href="https://pasteapp.io/paste-5?featured_on=pythonbytes">Paste 5</a></li> </ul> <p><strong>Joke</strong>: <a href="https://mkennedy.codes/posts/roosevelt-s-man-in-the-arena-but-for-developers/?featured_on=pythonbytes">Theodore Roosevelt’s </a><a href="https://mkennedy.codes/posts/roosevelt-s-man-in-the-arena-but-for-developers/?featured_on=pythonbytes"><strong>Man in the Arena</strong></a><a href="https://mkennedy.codes/posts/roosevelt-s-man-in-the-arena-but-for-developers/?featured_on=pythonbytes">, but for programming</a></p>

May 26, 2025 08:00 AM UTC


Brian Okken

Unravelling t-strings with pytest

Brett Cannon recently released a great article explaining how Python 3.14’s new t-strings work.

Here’s the article: Unravelling t-strings.

He built up the functionality of how t-strings work in a way that you can follow along even if you don’t have 3.14.0b1 (where t-strings are instroduced), all the way up to the last example.

He walks through

The end result is very close to an example used in PEP 750.

May 26, 2025 01:22 AM UTC