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 for our integration project
- Defining a Salesforce connection in Zato
- Authoring a service that will map input data to the format that Salesforce expects
- Creating a Zato REST channel that will be invoked through curl during tests
- Testing the integration
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:
- Username and password
- Consumer key
- Consumer secret
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 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.
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.
1 post - 1 participant
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:

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:
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:
And why not confirm the rest as well?
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:
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
:
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.
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:
And here's the visual representation:
You started with the three lists
names
,ages
, andlocations
.You zipped them together to create groupings, each containing a name, an age, and a location.
The zipping enabled you to sort these three-tuples (tuples each containing three items) using the person's age.
Finally, you unzipped the result to get back to having three separate iterables:
names
,ages
, andlocations
. Since these iterables are returned by thezip()
call, they're tuples, not lists. But you can cast them to lists if that's what you want.
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 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.
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
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 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 ]
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
macOS: Correctly recognize self-dependencies of DLLs that include an architecture suffix on
x86_64
. (Fixed in 2.6.1 already.)Standalone: Resolved an issue where
.pyi
files associated with versioned extension module filenames were not detected. (Fixed in 2.6.1 already.)Standalone: Fixed
.pyi
file parsing failures caused by single-line triple quotes. (Fixed in 2.6.1 already.)Standalone: Corrected the
__spec__.origin
path for packages where__init__
is an extension module.While this primarily affected the module’s
repr
string, code relying onorigin
for resource location could previously encounter errors. (Fixed in 2.6.1 already.)Multidist: Ensured created binaries use the name they were launched with, rather than the path of the actual binary file.
This allows entry points invoked via
subprocess
with different process names to function correctly from a single binary distribution. (Fixed in 2.6.1 already.)Windows: Fixed unusable
sys.stdin
when attaching to a console (--windows-console-mode=attach
) without an active terminal, which previously led to errors when forking processes. (Fixed in 2.6.1 already.)Modules: Prevented crashes in module mode when encountering potentially optimizable
importlib
distribution calls that are non-optimizable in this mode. (Fixed in 2.6.1 already.)Python3: Resolved errors with newer
setuptools
versions caused by namespace packages not providing apath_finder
. (Fixed in 2.6.1 already.)Windows: Removed a UTF-8 comment from a C header file that could prevent MSVC from compiling correctly in certain system locales. (Fixed in 2.6.2 already.)
Standalone: Ensured that user-inhibited data files have their associated tags cleared to prevent confusion in plugins. (Fixed in 2.6.2 already.)
Standalone: Corrected
pkgutil.iter_modules
to prevent incomplete module listings.Previously, if a package directory existed, Python’s file finder could interfere, yielding incomplete results that excluded compiled modules. (Fixed in 2.6.2 already.)
Windows: Addressed a C compiler warning related to onefile compression on 32-bit Windows during bootstrap compilation.
Python3.12+: Resolved a
SystemError
when accessing themodule
attribute oftype
variables.Metadata: Handled cases where reading record data from distribution files might fail because the files do not always exist. (Fixed in 2.6.3 already.)
Compatibility: Fixed dependency resolution for shared libraries that are symbolic links on non-Windows platforms.
Resolution failed if dependencies originated from the symlink target and that target was not in the system library path or nearby as another symlink. (Fixed in 2.6.3 already.)
Standalone: Prevented the use of
msvcp140.dll
fromPySide6
(specificallyshiboken6
) by other packages to avoid potential compatibility crashes. (Fixed in 2.6.3 already.)Standalone: Fixed
.pyi
file parsing failures caused by comments appearing afterimport
statements. (Fixed in 2.6.3 already.)UI: Corrected the output of DLL and EXE listings, which no longer provided the correct sub-folder information. (Fixed in 2.6.3 already.)
macOS: Restored the attachment of icons to application bundles. (Fixed in 2.6.3 already.)
Onefile: Resolved a C compiler warning on 32-bit Windows related to missing type conversion for the decompression buffer size. (Fixed in 2.6.3 already.)
Python3.12+: Ensured type aliases have a usable
module
attribute containing the correct module name. (Fixed in 2.6.3 already.)Python3.12+: Improved compatibility for type alias values.
Corrected the creation process for compound type aliases, resolving errors in libraries like
pydantic
when used in schemas. (Fixed in 2.6.5 already.)Python3.12+: Implemented a workaround for extension modules failing to set the correct package context, which previously caused significant compatibility issues (e.g., namespace collisions).
This was particularly problematic when static libpython was unavailable (common on Windows and official macOS CPython). The workaround involves saving/restoring the module context and correcting potentially wrongly named sub-modules created during extension module loading.
This improves compatibility for packages like
PySide6QtAds
,onnx
,mediapipe
,paddleocr
, and newerscipy
versions, which were previously affected. (Fixed in 2.6.6 already.)Plugins: Prevented a crash in the Nuitka Package Configuration helper function
get_data
when falling back topkgutil
for data files not found naively. (Fixed in 2.6.6 already.)Standalone: Corrected the rpath value used for finding dependent DLLs in sub-folders on non-Windows platforms.
It previously excluded the DLL’s own folder (
$ORIGIN
), sometimes preventing DLLs from loading. (Fixed in 2.6.7 already.)Standalone: Preserved existing
$ORIGIN
-relative rpaths in DLLs on Linux.Some PyPI packages rely on these existing paths to reference content in other packages; replacing them previously broke these setups. (Fixed in 2.6.7 already.)
Standalone: Now treats shared library dependencies specified with paths as implicit rpaths.
This fixes builds using Python distributions like Python Build Standalone (installed by
uv
), which may have an$ORIGIN
-relativelibpython
dependency that needs to be respected. (Fixed in 2.6.7 already.)Python3: Ensured generators preserve external exceptions and restore their own upon resuming.
This fixes issues where generators used as context managers, handling an exception via
yield
, could prevent an outerwith
statement from correctly re-raising its own exception. (Fixed in 2.6.7 already.)Android: Removed the Termux
rpath
value pointing into its installation from standalone builds.While ineffective after APK packaging due to Android security, this value should not have been present. (Fixed in 2.6.7 already.)
Python Build Standalone: Added the rpath to
libpython
by default in all modes for Python Build Standalone distributions.This resolves issues with uninstalled
libpython
on Linux. (Fixed in 2.6.8 already.)Standalone: Resolved incompatibility with older Linux distributions caused by using newer
patchelf
options introduced in 2.6.8’s rpath changes. (Fixed in 2.6.9 already.)Python3.9: Fixed errors in the
spacy
plugin when using olderimportlib.metadata
versions. (Fixed in 2.6.8 already.)Standalone: Prevented
requests
package imports from being incorrectly treated as sub-packages. (Fixed in 2.6.8 already.)Distutils on macOS: Improved integration for scanned extension modules where determining the correct architecture can be difficult. (Fixed in 2.6.8 already.)
Windows: Defined
dotnet
as a dependency to ensure all UI features requiring it are properly enabled.Scons: Ensured the correct
link.exe
executable is used for the MSVC backend, avoiding potential conflicts with linkers added to thePATH
(e.g., bygit
). (Fixed in 2.6.9 already.)Scons: Avoided using
config.txt
withclcache
when using MSVC.This prevents potential race conditions during the first use where multiple
clcache
threads might attempt to create the file simultaneously. (Fixed in 2.6.9 already.)Standalone: Ensured extension modules are loaded during the
create_module
phase of the Nuitka loader for better compatibility.Loading them later during
exec_module
caused issues with some extension modules, such as those created bymypy
(used inblack
).Python3.13: Corrected the workaround for extension module package context issues, resolving errors that occurred when the module and package names were identical.
Module: Prevented stub generation attempts for namespace packages, which previously resulted in warnings as there is no source code to process.
Debian: Ensured consistent casing for the installer name used in Debian package metadata.
Poetry: Updated detection logic for newer
poetry
versions to handle changes in installer name casing, which could previously impact system DLL usage determination.Module: Improved stub generation (
stubgen
) for generics, handling of missingtyping
imports, and other cases.Plugins: Fixed potential corruption and crashes in the
dill-compat
plugin when handling functions with keyword defaults.Standalone: Added support for newer
py-cpuinfo
versions on non-Windows platforms.Accelerated: Prevented Nuitka’s
sys.path_hook
from overriding standard Python path loader hooks, as it doesn’t yet support all their functionalities.Python3.12.7+: Set additional unicode immortal attributes (including for non-attributes) to prevent triggering Python core assertions when enabled.
Compatibility: Ensured errors are properly fetched during class variable lookups.
Previously, an error exit could occur without an exception being set, leading to crashes when attempting to attach tracebacks.
Python3.13: Adapted dictionary value creation and copying to follow internal layout changes, preventing potential crashes and corruption caused by using obsolete Python 3.11/3.12 code.
Scons: Corrected the default LTO module count calculation to refer to the number of compiled modules.
Package: Ensured namespace parent modules are included in compiled packages.
These were previously missed due to the removal of reliance on
--include-package
for delayed namespace package handling.macOS: Ensured data files included in application bundles are also signed.
Windows: Applied short path conversion to the directory part of
sys.argv[0]
as well.This prevents issues with tools called using this path that might not handle non-shortened (potentially unicode) paths correctly.
Package Support
Standalone: Included necessary data files for the
blib2to3
package. (Added in 2.6.1 already.)Standalone: Added support for newer
numba
versions. (Added in 2.6.2 already.)Standalone: Added support for newer
huggingface_hub
versions. (Added in 2.6.2 already.)Anti-Bloat: Provided additional
numpy.testing
stubs required for proper execution of somesklearn
modules. (Fixed in 2.6.2 already.)Standalone: Enhanced configuration for
fontTools
. Avoided configuring hidden dependencies now detected by parsing provided Python files like.pyi
files. (Fixed in 2.6.2 already.)Standalone: Corrected plugin configuration for newer
PySide6
sqldrivers
on macOS. (Fixed in 2.6.3 already.)Python3.12+: Introduced standalone support for
mediapipe
, including a workaround for extension module sub-module creation issues. (Fixed in 2.6.3 already.)Python3.12+: Introduced standalone support for
onnx
, including a workaround for extension module sub-module creation issues. (Fixed in 2.6.3 already.)Standalone: Added support for newer
sqlglot
versions. (Added in 2.6.5 already.)Standalone: Included
asset
data files for thearcade
package. (Added in 2.6.5 already.)Standalone: Added implicit dependencies for
sqlalchemy.orm
. (Added in 2.6.5 already.)macOS: Included additional frameworks required for PySide 6.8 web-engine support. (Added in 2.6.5 already.)
Standalone: Enhanced
cv2
support to handle potentially Python minor version-specific config files by allowing optional data file discovery in plugins. (Added in 2.6.6 already.)Standalone: Added support for the
scipy
sub-module loader mechanism.By treating it as a lazy loader, implicit dependencies within
scipy
are now correctly detected without requiring explicit configuration. (Added in 2.6.7 already.)Standalone: Automatically include Django database engine modules. (Added in 2.6.7 already.)
Homebrew: Added
tk-inter
support for Python versions using Tcl/Tk version 9.Standalone: Included a missing data file for the
jenn
package.Standalone: Added support for newer
scipy.optimize._cobyla
versions. (Fixed in 2.6.8 already.)Anaconda: Fixed issues with bare
mkl
usage (withoutnumpy
).Standalone: Included a missing data file for the
cyclonedx
package.Compatibility: Enabled pickling of local compiled functions using
cloudpickle
andray.cloudpickle
.Standalone: Added support for
mitmproxy
on macOS.Standalone: Included necessary data files for
python-docs
andmne
.Standalone: Added support for newer
toga
versions, requiring handling of its lazy loader.Standalone: Introduced support for the
black
code formatter package.Standalone: Included metadata when the
travertino
package is used.Standalone: Significantly enhanced support for detecting dependencies derived from
django
settings.Standalone: Added support for the
duckdb
package.
New Features
DLL Mode: Introduced a new experimental mode (
--mode=dll
) to create standalone DLL distributions.While functional for many cases, documentation is currently limited, and features like multiprocessing require further work involving interaction with the launching binary.
This mode is intended to improve Windows GUI compatibility (tray icons, notifications) for onefile applications by utilizing an internal DLL structure.
Windows: Onefile mode now internally uses the new DLL mode by default, interacting with a DLL instead of an executable in temporary mode.
Use
--onefile-no-dll
to revert to the previous behavior if issues arise.Windows: Added support for dependency analysis on Windows ARM builds using
pefile
(as Dependency Walker lacks ARM support).Android: Enabled module mode support when using Termux Python. (Added in 2.6.7 already.)
Compatibility: Added support for Python Build Standalone distributions (e.g., as downloaded by
uv
).Note that static
libpython
is not supported with these distributions as the included static library is currently unusable. (Added in 2.6.7 already.)Windows: Enabled taskbar grouping for compiled applications if product and company names are provided in the version information. (Added in 2.6.4 already.)
Windows: Automatically use icons provided via
--windows-icon-from-ico
forPySide6
applications.This eliminates the need to separately provide the icon as a PNG file, avoiding duplication.
Nuitka Package Configuration: Allowed using values from
constants
andvariable
declarations withinwhen
conditions where feasible.Reports: Clearly indicate if an included package is “vendored” (e.g., packages bundled within
setuptools
).Compatibility: Added support for the
safe_path
(-P
) Python flag, preventing the use of the current directory in module searches.Compatibility: Added support for the
dont_write_bytecode
(-B
) Python flag, disabling the writing of.pyc
files at runtime (primarily for debugging purposes, as compiled code doesn’t generate them).UI: Introduced a new experimental tool for scanning distribution metadata, producing output similar to
pip list -v
. Intended for debugging metadata scan results.Plugins: Enhanced the
dill-compat
plugin to transfer__annotations__
and__qualname__
.Added an option to control whether the plugin should also handle
cloudpickle
andray.cloudpickle
.AIX: Implemented initial enhancements towards enabling Nuitka usage on AIX, although further work is required.
Optimization
Optimized finalizer handling in compiled generators, coroutines, and asyncgens by avoiding slower C API calls introduced in 2.6, restoring performance for these objects.
Implemented a more compact encoding for empty strings in data blobs.
Instead of 2 bytes (unicode) + null terminator, a dedicated type indicator reduces this frequent value to a single byte.
Anti-Bloat
Avoided including
matplotlib
when used by thetqdm
package. (Added in 2.6.2 already.)Avoided including
matplotlib
when used by thescipy
package. (Added in 2.6.2 already.)Avoided including
cython
when used by thefontTools
package. (Added in 2.6.2 already.)Avoided including
sparse
when used by thescipy
package. (Added in 2.6.3 already.)Avoided including
ndonnx
when used by thescipy
package. (Added in 2.6.3 already.)Avoided including
setuptools
for thejaxlib
package.Also prevented attempts to query the version from
jaxlib
source code using git. (Added in 2.6.3 already.)Avoided including
yaml
when used by thescipy
package. (Added in 2.6.4 already.)Avoided including
charset_normalizer
for thenumpy
package. (Added in 2.6.5 already.)Avoided including
lxml
for thepandas
package. (Added in 2.6.5 already.)Avoided including
PIL
(Pillow) for thesklearn
package. (Added in 2.6.5 already.)Avoided including
numba
when used by thesmt
package. (Added in 2.6.7 already.)Avoided including more optional
pygame
dependencies. (Added in 2.6.8 already.)Avoided including
setuptools
,tomli
, andtomllib
for theincremental
package.Avoided including
IPython
when used by therich
package vendored withinpip
.For reporting purposes, treated usage of
ipywidgets
as equivalent to usingIPython
.Added support for
assert_raises
within Nuitka’snumpy.testing
stub.
Organizational
UI: Improved the output format for used command line options.
Filenames provided as positional arguments now use the report path format. Info traces now support an optional leader for intended value output, enhancing readability.
Reports: Saved and restored timing information for cached modules.
This eliminates timing differences based on whether a module was loaded from cache, reducing noise in Nuitka-Watch comparisons where cached module timings previously changed with every new compilation.
Actions: Added compilation report artifacts to all CI runs involving empty module compilations.
Debugging: Enabled the
--edit
option to find modules within.app
bundles created on macOS. (Added in 2.6.1 already.)User Manual: Updated the Nuitka-Action example; linking directly to its documentation might be preferable. (Changed in 2.6.1 already.)
Quality: Enforced ASCII encoding for all Nuitka C files to prevent accidental inclusion of non-ASCII characters.
Quality: Added syntax validation for
global_replacements
result values, similar to existing checks forreplacements
.Also added validation to ensure
global_replacements_re
andreplacements_re
result values are valid regular expressions.Plugins: Ensured error messages for illegal module names in implicit imports correctly report the originating plugin name.
Quality: Enabled use of
clang-format-21
if available and applied formatting changes specific to this newer version.Quality: Suppressed
pylint
warnings related tosetuptools
usage when running with Python 3.12+.UI: Disallowed mixed usage of Anaconda and Poetry without an active Poetry virtual environment.
This avoids issues caused by a Poetry bug where it incorrectly sets the
INSTALLER
metadata for Conda packages in this scenario, making reliable detection of Conda packages impossible.macOS: Deprecated
--macos-target-arch
in favor of the standard--target-arch
option, with plans for eventual removal.Release: Ensured usage of a compatible
setuptools
version duringosc
uploads.UI: Improved the error message for invalid custom anti-bloat modes by listing the allowed values.
Release: Removed CPython test git submodules from the repository.
These submodules caused issues, such as being cloned during
pip install
and sometimes failing, potentially breaking Nuitka installation.
Tests
Enabled passing extra options via the
NUITKA_EXTRA_OPTIONS
environment variable fordistutils
test cases involvingpyproject.toml
.Removed the standalone test for the
gi
package, as it’s better covered by Nuitka-Watch and prone to failures in CI due to lack of an X11 display.Ensured tests correctly ignore the current directory when necessary by using the new
--python-flag=safe_path
.This forces the use of the original source code as intended, rather than potentially finding modules in the current directory.
Corrected the implementation of the
retry
mechanism forpipenv
installation within thenuitka-watch
tool.Added support for passing extra options via environment variables to the
nuitka-watch
tool.
Cleanups
Distutils: Standardized usage to
--module=package
where appropriate, instead of manually adding package contents, resulting in more conventional Nuitka command lines.Refactored
.pyi
file creation into a dedicated function, simplifying the post-processing code.
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 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:
rfc3986
h2
(andhpack
,priority
,hyperframe
, etc)requests
psl
(now un-maintained)
Here are the projects that I consider feature-complete and will only be making security-fix releases as necessary going forward:
brotlicffi
brotlipy
hstspreload
socksio
And here are the projects that I plan to continue to maintain beyond only security releases:
urllib3
truststore
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 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 withfor
andwhile
, exception handling withtry
…except
, and structural pattern matching withmatch
…case
. - 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 PythonIn 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:
- If the order is less than $150.00, then the customer doesn’t get free shipping.
- 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:
>>> 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!
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:
greeting.py
print("Hello!")
print("Hello!")
print("Hello!")
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:
>>> for _ in range(3):
... print("Hello!")
...
Hello!
Hello!
Hello!
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 ]
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 ]
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

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.
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 27, 2025
PyCoder’s Weekly
Issue #683: Narwhals, ty, LangChain, and More (May 27, 2025)
#683 – MAY 27, 2025
View in Browser »
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)
Senior Software Engineer – Quant Investment Platform (LA or Dallas) (Los Angeles, CA, USA)
Causeway Capital Management LLC
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
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
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
Events
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 »
[ 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 ]
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:
- What scopes are and how they work in Python
- Why it’s important to know about Python scope
- What the LEGB rule is and how Python uses it to resolve names
- How to modify the standard behavior of Python scope using
global
andnonlocal
- What scope-related tools Python offers and how you can use them
[ 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 ]
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.
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 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
- PEP 649: The evaluation of type annotations is now deferred, improving the semantics of using annotations.
- PEP 750: Template string literals (t-strings) for custom string processing, using the familiar syntax of f-strings.
- PEP
784: A new module
compression.zstd
providing support for the Zstandard compression algorithm. - PEP
758:
except
andexcept*
expressions may now omit the brackets. - Syntax highlighting in PyREPL, and support for color in unittest, argparse, json and calendar CLIs.
- PEP 768: A zero-overhead external debugger interface for CPython.
- UUID
versions 6-8 are now supported by the
uuid
module, and generation of versions 3-5 and 8 are up to 40% faster. - PEP
765: Disallow
return
/break
/continue
that exit afinally
block. - PEP 741: An improved C API for configuring Python.
- A new type of interpreter. For certain newer compilers, this interpreter provides significantly better performance. Opt-in for now, requires building from source.
- Improved error messages.
- Builtin implementation of HMAC with formally verified code from the HACL* project.
- A new command-line interface to inspect running Python processes using asynchronous tasks.
- The pdb module now supports remote attaching to a running Python process.
(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
- PEP 761: Python 3.14 and onwards no longer provides PGP signatures for release artifacts. Instead, Sigstore is recommended for verifiers.
- Official macOS and Windows release binaries include an experimental JIT compiler.
Incompatible changes, removals and new deprecations
- Incompatible changes
- Python removals and deprecations
- C API removals and deprecations
- Overview of all pending 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
- Online documentation
- PEP 745, 3.14 Release Schedule
- Report bugs at github.com/python/cpython/issues
- Help fund Python and its community
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
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:
$ python -m pip install marimo
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 NotebookThis 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:
$ python -m pip install numpy
With NumPy installed, you can now create your notebook by typing the following command into your console:
$ marimo edit simultaneous_equations.py
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:

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 ]
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 ]
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>
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
- Evaluating the Python expression
- Applying specified conversions
- Applying format specs
- Using an Interpolation class to hold details of replacement fields
- Using Template class to hold parsed data
The end result is very close to an example used in PEP 750.