Planet Python
Last update: August 15, 2026 09:48 PM UTC
August 15, 2026
Juri Pakaste
TIL: Typed do in Swift
TL;DR: Swift has do throws(MyError). It's helpful.
This is one of those "I can't believe I had missed that" things.
I've been pretty enthusiastic about adopting typed throws in Swift. If you need to process certain kinds of errors, it just makes sense to me. However, it was always a bit painful. You have this:
do {
// throwing code here
} catch {
// error is precisely typed here, assuming
// the block above throws just one type
}
… and all is fine and good, except that the "throwing code" doesn't need to
grow particularly complex before Swift decides to widen the type to any Error
and your catch block starts producing compilation errors.
When I ran into this, I'd use catch let error as MyError. Fine. Except it
isn't, because now your error-handling code is incomplete: you have to add
an unreachable catch-all block, and you're unhappy with the language and
your life choices.
I did page through the enhancement proposal when it passed through Swift Evolution, but I just never noticed that it doesn't end there. What you do in this situation is this:
do throws(MyError) {
// throwing code here
} catch {
// now the compiler doesn't get confused and
// all is well-typed unicorns and sunshine
}
Now you know too.
August 13, 2026
PyCharm
Open weight models are having a moment, driven by control, choice, and cost. Hybrid and local AI are now getting serious looks, so JetBrains teamed up with DeepLearning.AI on a free AI Coding Workflows: Hybrid to Local course that covers the ideas and options. The course is now available and uses PyCharm and its AI […]
Python Software Foundation
Announcing the Packaging Council Election Candidates for 2026!
PyCharm
Hybrid and Local AI course at DeepLearning.AI
Python Software Foundation
Announcing the PSF Board Candidates for 2026!
Wingware
Wing Python IDE Version 12.0.2 - August 13, 2026
Wing Python IDE version 12.0.2 has been released. This release adds a native ARM64 Windows version of Wing, improves remote development on Windows, streamlines Claude Code setup, and improves performance and responsiveness, particularly when working with very large projects and on Windows. It also reduces the size of the analysis cache database by about 20% and fixes a number of bugs. See the change log for details.
Wing 12 integrates the Claude Code AI coding agent directly into the IDE, with a new Claude Code tool, a Tasks tool for planning and reviewing AI agent work, and a set of MCP servers that give the agent access to Wing's source code analysis, unit testing, debugger, and code review features. See the list of Wing 12 features below for details.

Downloads
Wing 12 -- the full Python IDE, available as Wing Pro (for agentic development) or Wing Classic (for manual development) depending on your license, with a free 30-day trial of Wing Pro.
Wing 101 v. 12 -- a simplified free Python IDE for teaching beginning programmers.
Wing 11 and earlier versions are not affected by installation of Wing 12 and may be installed and used independently. However, project files for Wing 11 and earlier are converted when opened by Wing 12 and should be saved under a new name, since Wing 12 projects cannot be opened by older versions of Wing.
New in Wing 12
AI Coding Agent Integration with Claude Code
Wing 12 adds a Claude Code tool that integrates the Claude Code AI coding agent with the IDE. Set Up for Claude Code in the Project menu configures the active project for AI agent development.
A set of MCP (Model Context Protocol) servers gives Claude Code access to Wing's source code analysis, testing, and debugger functionality, so the agent can more efficiently navigate and understand your code, write, run, and fix unit tests, and use the debugger to diagnose difficult runtime errors. In our benchmarks, giving Claude Code access to Wing's MCP servers made agent-driven coding tasks both faster and cheaper.
Tasks Tool
The new Tasks tool lets you plan, queue, execute, review, and audit the history of AI agent development tasks, making it easier to supervise and inspect the agent's work before committing it to revision control.
FIX Features and Write Tests
Wing 12 adds AI agent driven FIX features that hand the current debugger bug, failing unit tests, or code warnings to Claude Code for resolution. New Write Tests items in the Testing and editor context menus prompt the agent to write unit tests for selected code.
Code Actions
Wing 12 also adds AI Code Actions, accessed from the FIX icon in the editor toolbar, that operate on selected code or the enclosing scope. Built-in actions include explaining code, reviewing it for quality or security risks, fixing code warnings, optimizing for performance, and updating comments and docstrings. The action list is user-extensible, so you can add your own prompts for tasks you run often.
Pseudo-Terminal for OS Commands and Debug I/O
The OS Commands and Debug I/O tools now default to using a pseudo-terminal that implements full ANSI terminal emulation, so you can run and debug programs that use color output, cursor positioning, or full-screen TUIs.
Redesigned OS Commands Capability
The OS Commands tool has been replaced with configurable OS Commands in the Tools menu. Each OS Command acts like its own tool, for use in any tool or editor split.
Tools in Editor Splits and Reorganized Tools Menu
Tools can now also be added or dragged to editor splits, allowing for much more flexible workspace layout. The Tools menu has been reorganized into related groups, with less-used and legacy tools in an Other sub-menu, so more commonly used tools are easier to find.
Test Discovery and Preferences Search
Wing 12 adds automatic test file discovery and discovery of individual unit tests within files, so you usually don't need to specify test file patterns or add test files individually. The Preferences dialog now supports text search and back/forward navigation.
Other Minor Features and Improvements
Wing 12 also includes many other improvements, including:
- IDE build for ARM64 Windows
- Improved performance and responsiveness
- Improved remote agent installation and remote development
- Significantly faster source code analysis
- Prompts for SSH passphrases and HTTPS credentials when needed during VCS operations
- Faster detection of externally modified files, with reduced CPU load
- Saving and restoring of tool console scrollback across project close and reopen
- Clickable OSC 8 hyperlinks in the OS Commands and Debug I/O tools
- A preference to select the ssh or plink.exe SSH implementation
- A notice on the next startup when Wing's previous session ended in an unexpected crash
Wing 12 also makes a number of other bug fixes and usability improvements.
Product Line Changes
Wing 12 simplifies the product line. The Commercial / Non-Commercial use distinction has been replaced by two feature-based product tiers:
- Wing Pro -- the full-featured Python IDE including AI agent development tools
- Wing Classic -- the complete traditional Python IDE for hands-on development, with no AI agent features
Anyone may purchase either tier for any purpose. Existing Commercial and Non-Commercial Use licenses both become Wing Pro. Customers who don't need the AI agent features may move to Wing Classic at renewal time, or any time sooner by contacting support@wingware.com.
Wing Personal has been discontinued. Existing Wing Personal users may continue to use Personal 11.x indefinitely, switch to free Wing 101, or purchase a Wing Classic license. See Pricing for details.
Changes and Incompatibilities
The single-LLM-query AI features originally introduced in Wing 11 (the AI Coder and AI Chat tools) are considered legacy in Wing 12 and hidden from the user interface by default. They remain available in projects that already use them and can be re-enabled with Project Properties > AI in Project Properties or in the Projects > AI preferences.
See Wing's Claude Code Agent Integration for Wing 12's AI agent approach.
If you have questions, please don't hesitate to contact us at support@wingware.com.
Trey Hunner
Reorganizing Python's sys module
Python Insider
Announcing the Packaging Council Election Candidates for 2026!
Announcing the 17 nominees for the inaugural Python Packaging Council election, and how to vote in the election.
August 12, 2026
Django Weblog
DSF Office Hours
The DSF Board hosts open office hours every Wednesday at 6:00 PM UTC (check your local time). Anyone in the Django community is welcome to drop in. You do not need an agenda or an invitation. Video call details are on the DSF Office Hours page.
We have been running these since October 2024, and right now we have two things we would especially like to talk with you about.
Who shows up
On any given Wednesday, you might find DSF Board members, Steering Council members, Django Fellows, working group members, and community members who are curious about joining a working group. There is no membership requirement. Anybody from the community can join, and often does.
The Executive Director search
We recently published a call for applicants for a Django Executive Director. If you are considering applying, or you are still deciding whether it is the right fit, come to office hours and ask us anything: what the job actually looks like, what we expect in the first year, how the search works. We would rather answer your questions directly than have you guess from a job posting.
If this is the first you are hearing about the search, please help us spread the word. The best candidate may be someone who has not thought to look.
Fundraising to support it
Hiring an Executive Director is why we raised our 2026 fundraising goal from $300,000 to $500,000, which needs about $16,000 per month in additional recurring support. We have made real progress and are working to close the rest.
If your company uses Django, you can help through corporate sponsorship, a direct donation, or GitHub Sponsors. Most of these are a small lift for a company that already depends on Django. If you want fundraising materials to bring to your leadership team, or help picking the option that fits, come to office hours, and we will get you what you need.
Everything else
Office hours cover plenty beyond that: what our working groups are up to and how to join one, projects the Foundation is working on, and whatever you have been wondering about how the DSF operates. In the weeks before a board meeting, we use the time to gather feedback on what we are about to discuss. If you want the board to hear something, this is a direct line.
One thing office hours are not: a general Django support channel. It is not the place to debug your code or market a product. For coding help, the Django Forum will get you faster answers.
Office hours are the most direct way to keep up with the Foundation, but they are not the only one. We wrote up all the other places we post and where the conversation happens if Wednesdays do not work for you.
Otherwise, put a Wednesday on your calendar and say hello.
PyCharm
What’s New in PyCharm 2026.2.1
This PyCharm release is a big one for anyone building with AI. Your agents can now roll up their sleeves inside your Jupyter notebooks – working against a live kernel instead of firing off disconnected scripts. And they finally know which Python to use, so packages land in the right environment every time. We’re also […]
We Stopped AI Agents From Installing Into the Wrong Python: Task Success Rates Jumped to 95%+
AI agents are supposed to save you time. Ask one to install a dependency or run your project, though, and it often does the opposite: It installs into the wrong Python, ignores the uv or virtual environment your project uses, and hands back a broken setup for you to fix yourself. PyCharm’s new Agent Environment […]
We Gave AI Agents a Live Jupyter Kernel in PyCharm
If you’ve handed notebook work to an AI agent, you know how it tends to go: More often than not, it corrupts your .ipynb, loses your trained model the moment the run finishes, or burns budget sitting idle through a long job while you watch. To solve this, we’re introducing a brand-new Jupyter skill. Built […]
Unbundling and Deprecating Low-Usage Plugins in PyCharm
As part of ongoing maintenance, we are unbundling and deprecating low-usage plugins starting with PyCharm 2026.2. This includes support for Data Wrangler, Hugging Face, and Google Colab, among others. A more focused set of bundled plugins means a leaner codebase, enabling us to keep PyCharm fast and responsive and invest our effort where it has […]
Python GUIs
Adding QTabWidget to a Layout Alongside Other Widgets in PyQt6 — How to combine QTabWidget with other layouts without it taking over your entire window
I'm trying to create a layout in PyQt and implement different layouts into one QHBoxLayout. Everything works fine, but when I add a QTabWidget alongside other layouts, it becomes the only visible widget — as if everything else disappears. Isn't it possible to have a QTabWidget in a layout beside other layouts?
Python Bytes
#491 Feeling Judged
Topics include , Post-quantum crypto lands in Python, MCP goes stateless — and FastMCP gets renamed, and inshellisense - IDE style command line auto complete.
Python Insider
Python 3.12.14, 3.11.16 and 3.10.21 are now available!
[Python Releases Eclipse Your Security!](https://discuss.python.org/t/python-3-12-14-3-11-16-and-3-10-21-are-now-available/108537) New security releases for 3.10, 3.11 and 3.12 are now available.
August 11, 2026
TestDriven.io
Storing Django Static and Media Files on Cloudflare R2
This tutorial shows how to configure Django to load and serve up static and media files, public and private, via Cloudflare R2.
PyCoder’s Weekly
Issue #747: Modern OOP, bisect, Django Async, and More (2026-08-11)
August 10, 2026
Brett Cannon
My nomination statement for the 2026 Python packaging council
I have decided to run for the inaugural/2026 Python packaging council (PPC). I will say I have the support of my employer (Microsoft) to do this, but they didn&apost ask me to and my usual thing that I would quit before I let any employer pressure me
James Bennett
Breaking up (lines) is hard to do
Here’s a seemingly simple question: given a chunk of multi-line text, how do you split it and return an array whose members are the constituent lines of the text?
Hopefully, your first instinct is to reach for some sort of standard-library function, maybe something like the splitlines() method of Python’s str type. Because it turns out this “simple” question is actually pretty complex to answer! For example, quite some time ago I read a post by William Woodruff pointing out the surprising discovery that Python treats up to eleven different Unicode code points or code point sequences as indicating a line break.
At the time I meant to write about that, but a lot of other things started fighting for my time, and it’s only now that I’m finally digging it out of my drafts. Still, better late than never, so today let’s dig into some of the many ways there are to break a line of text and how they’ve been standardized and specified and ultimately wound up in the set Python uses.
In the beginning…
Once upon a time, there was ASCII. Of course there were other things before ASCII, and alongside ASCII, but for today’s discussion we really only need to go back to ASCII; if you want the full history of physical teletypes, how they evolved from typewriters and influenced character sets for computing and so on, I suggest Wikipedia. Here, I’m just going to gloss over and simplify a lot of that to focus on the topic at hand.
So. Once upon a time, there was ASCII. And it wound up being incredibly influential and important in computing, to an extent other early character sets couldn’t match. And because it was used on computers which used teletypes (basically electronic typewriters connected as input/output devices) as a user interface, it contained control characters for sending commands to the teletype. Such as a LINE FEED (byte value 0x0A) to advance the paper vertically to the next line, and a CARRIAGE RETURN (byte value 0x0D) to re-align the print head/carriage with the horizontal start point of the line.
These are often abbreviated LF and CR (or by their C-family escape sequences \n and \r, respectively), and you might think that since physically advancing a typewriter-style device to be ready to print the next line requires both operations, that would have just become the universal way everybody did new lines. Or at least the universal way everybody did them in English, or in the US, where ASCII dominated. Right?
Well, nothing is ever that simple. Physical teletypes apparently benefited from the two-character approach (as opposed to a single “new line” character) because it gave them time to physically move everything into the right position. But as virtual teletypes—“printing” to a television-like display instead of to paper—became more common, that was less of an issue. So there were multiple possible options for representing line breaks, and several of them showed up in historical systems. For example:
- CP/M used
CR LF. And so MS-DOS, which aimed for compatibility with it, usedCR LFtoo. And so Microsoft Windows, which wanted to be compatible with MS-DOS, also used it. - Meanwhile, Multics chose to use just
LFwith noCR, and Unix went along with that choice. - But Commodore and Apple and many others went yet another way and used plain
CR, with noLF.
This meant “plain text” was not easily portable between these various systems, since none of them could agree on how to represent a line break. Which led to one of my all-time favorite programming jokes, in the infamous “NOT the comp.text.sgml FAQ” document:
Q. What’s an RE?
A. RE is an acronym for Record End, which is sort of like a newline, only different. Goldfarb’s First Law of Text Processing states that:
“… if a text processing system has bugs, at least one of them will have to do with the handling of input line endings.”
[The Handbook, footnote p. 321]
The Record End concept was introduced to make sure that SGML parsers don’t violate Goldfarb’s First Law.
(for the uninitiated, Charles Goldfarb created SGML)
Anyway, over twenty years ago Python tried (in Python 2.3) to smooth this over by introducing “universal newline” mode for opening files, which accepts all three options: a plain \n (Unix), or a plain \r (classic Mac), or an \r\n sequence (DOS and Windows) will all be interpreted as line breaks.
But even in ASCII there there are other ways of breaking a line. For example, at byte value 0x0C ASCII includes the FORM FEED control character (FF, or \f). Which is not one of the traditional characters used by major operating systems as a “newline”, but nonetheless does cause a new line to occur: it moves to the next page (if necessary, by ejecting the current sheet of paper from the printer and feeding in a new one). And there’s also 0x0B, VERTICAL TAB (VT or \v): just as a “regular” tab (\t) causes a horizontal adjustment, a vertical tab causes a vertical one. So it, too, causes output to advance to another line (probably skipping several in the process).
And the C1 control characters added 0x85, the NEXT LINE character (typically abbreviated NEL), useful for translating back and forth between ASCII and IBM’s EBCDIC character set (which had “New Line” as a single character).
Then Unicode happened
Today we live in a Unicode world, and Unicode tries its hardest to catalog and standardize and describe how to work with all the world’s writing systems. Chapter 5, Section 8 of the Unicode Standard, “Newline Guidelines”, lists seven code points to recognize as causing new lines. Five of them we’ve seen already:
U+000A LINE FEED, from ASCIIU+000B LINE TABULATION, from ASCII’s vertical tabU+000C FORM FEED, from ASCIIU+000D CARRIAGE RETURN, from ASCIIU+0085 NEXT LINE, from the C1 control codes
The CR LF sequence is also recognized, on systems which use it.
But the other two code points are new and were created specifically for Unicode:
U+2028 LINE SEPARATOR(which Unicode likes to abbreviate asLS)U+2029 PARAGRAPH SEPARATOR(similarly abbreviated asPS)
The Unicode Standard explains that the traditional newline characters had started to become ambiguous, because of the rise of tools such as word-processing programs which implicitly broke lines to wrap them for display and so began using explicit “newline” characters to mean a paragraph break rather than a line break. So Unicode added two new code points whose purposes are explicit. And the standard says that “[I]n Unicode text, the PS and LS characters should be used wherever the desired function is unambiguous.”
This set of line-breaking code points originated in version 5.0 of Unicode, with Unicode Technical Report #13, which lists the seven “newline” code points and the CR LF sequence. This is also the set of code points and sequences defined for line boundaries in Unicode regular expressions, Unicode Technical Standard #18.
And expanding on Chapter 5 of the Standard, there’s Unicode Standard Annex #14, “Unicode Line Breaking Algorithm”. As the name implies, this document formally specifies the line-breaking algorithm for Unicode, including defining things like which characters offer an opportunity to break a line, whether the break is mandatory, and whether the break would come before or after the character in question. It does this in a typical Unicode way: by defining a set of named properties and specifying which characters have which properties.
Two ways about it
But there are still three “newline” characters supported by Python that we haven’t seen yet, and they come from a place that might be surprising: Unicode Standard Annex #9, the bidirectional algorithm. And it’s OK if you’re wondering what that has to do with newlines, because it’s not immediately obvious if you don’t already know about it.
Some written scripts, like the Latin script this blog post is written in, are written and read left-to-right: the start of a line of text is on the left-hand side, and the end is on the right-hand side. Other scripts, such as Arabic or Hebrew, do the opposite, and are right-to-left. And so Unicode, which again wants to cover all the world’s writing systems and let you use any or all of them, has to support both left-to-right and right-to-left horizontal text direction.
But more than that, it has to support switching direction within a single piece of text. You might have something that’s in, say, Arabic but quotes something in Spanish in the middle of a line; that would require a short section of left-to-right inside an otherwise right-to-left text. Or you might be writing something that uses boustrophedon, switching directions on each line. So Unicode includes direction-control characters like U+200E LEFT-TO-RIGHT MARK and U+200F RIGHT-TO-LEFT MARK to handle this. But it also needs to know the scope of a direction change, and that’s where the last “newline” characters come in: the Unicode bidirectional algorithm says that “[t]he effects of all of these formatting characters are limited to the current paragraph; thus, they are terminated by a paragraph separator”.
So Unicode characters have, among their properties, a “bidirectional class” which influences how they affect the bidirectional algorithm. And the characters which act as paragraph separators for purposes of ending the effects of an explicit directional marker all share a common value for this: bidirectional class B. The characters with that class include quite a few that we’ve already seen, along with three more characters:
U+001C INFORMATION SEPARATOR FOURU+001D INFORMATION SEPARATOR THREEU+001E INFORMATION SEPARATOR TWO
But these are better known by their original ASCII names: FILE SEPARATOR, GROUP SEPARATOR, and RECORD SEPARATOR. ASCII provided these to help represent data structures in memory and on storage media. Today it’s not as common to try to use control characters for this purpose, though they do have the virtue of being rare in actual text, unlike other common delimiters such as tab or comma.
End of the line
And now, after looking at multiple character sets and five Unicode technical documents, we can finally state clearly what’s going on in Python.
Python’s splitlines() treats ten different code points, and one multi-code-point sequence, as causing a line break. These are:
- The sequence
U+000D U+000A(CR LF). - The four code points which have line-breaking property
BK(Mandatory Break (Non-tailorable)):U+000B LINE TABULATION,U+000C FORM FEED,U+2028 LINE SEPARATOR, andU+2029 PARAGRAPH SEPARATOR. - The one code point which has line-breaking property
CR(Carriage Return (Non-tailorable)):U+000D CARRIAGE RETURN. - The one code point which has line-breaking property
LF(Line Feed (Non-tailorable)):U+000A LINE FEED. - The one code point which has line-breaking property
NL(Next Line (Non-tailorable)):U+0085 NEXT LINE. - The three code points which don’t have any of the above line-breaking properties, but do have bidirectional property
B:U+001C INFORMATION SEPARATOR FOUR,U+001D INFORMATION SEPARATOR THREE, andU+001E INFORMATION SEPARATOR TWO
Which is also exactly what’s stated by a comment in the CPython source code accompanying the list of individual code points that are considered to break lines, but hopefully now you have a better understanding of what that comment means and how this particular set was arrived at.
Talk Python to Me
#558: Hyper-Personal Software with Python
Every company has one. The little internal tool that Jane built back in 2021, and then Jane left. Nobody understands it, nobody will touch it. There are two unwritten rules around it: don't change it, it's working. And if you break it, you bought it. That's dark-matter enterprise software. For every app you can actually see, there are ten of these sitting in the shadows, frozen. Michael Booth thinks that just changed. He read my article on hyper-personal software and ran with it, writing about hyper-team software: small teams inside big companies finally building the tools that were never going to get built. We cover where this works, where it quietly goes wrong, and the guardrails that keep it from turning into a mess. Let's get into it.
Ed Crewe
From Routing Checks to Trajectory Testing: Evaluating an Agentic Chatbot
Django Weblog
Django is moving to an annual release cycle
Django's Steering Council has accepted the Django Enhancement Proposal DEP 20 to move Django to an annual release cycle. From January 2028, Django will make one feature release a year, giving every feature release the LTS-level three years of support, and version numbers will carry the feature release year: Django 2028, then Django 2029, and so on.
Better Python support
Python releases annually, each October. Django's eight-month cycle hasn't fit that well: LTS releases carried a wide Python matrix, including versions long past their upstream end-of-life.
Under the new cycle, each Django version supports the three latest Python versions at release, and picks up the new Python version during its first year. Django's support window ends in step with its oldest supported Python.
Every release is an LTS
Every feature release gets three years of support: one year of mainstream bugfixes, then two years of security and data-loss fixes. The "LTS" label is retired — every feature release now carries that same, unique commitment.
No more LTS gap: no racing a deadline to jump two years of changes at once. Upgrade one year at a time, whenever suits you within the support window. Three versions are supported at any time, giving third-party packages a clear, rolling target.
API stability and deprecation policies are unchanged — deprecation periods actually get longer in calendar terms.
Transition timeline
Django 2028 will be the first release under the new cycle.
| Release | Date | End of mainstream support | End of extended support |
|---|---|---|---|
| Django 6.1 | August 2026 | April 2027 | December 2027 |
| Django 6.2 LTS | April 2027 | December 2027 | April 2030 |
| Django 2028 | January 2028 | January 2029 | January 2031 |
| Django 2029 | January 2029 | January 2030 | January 2032 |
| Django 2030 | January 2030 | January 2031 | January 2033 |
Nothing changes before 2028. Support commitments for Django 5.2 LTS and 6.2 LTS stand as made.
Read the DEP
DEP 20 has the full specification and the reasoning behind each decision. Thanks to everyone who took part in the discussion, and to the Steering Council for its consideration.
August 09, 2026
Ned Batchelder
Caller-specific coverage
I’ve had an idea rattling around to get more detail from coverage measurement. Can we measure the coverage in a function separately for each caller of the function?
Here’s why I want it: in Acidica, my toy BASIC interpreter, I had code to implement the built-in functions that looked something like this:
match func_name:
case "LEN":
if len(args) != 1:
raise TypeError(f"Wrong arguments for LEN, got {len(args)}")
return len(args[0])
case "LEFT$":
if len(args) != 2:
raise TypeError(f"Wrong arguments for LEFT$, got {len(args)}")
return args[0][:args[1]]
# ... 19 other built-ins ...
I didn’t like the repeated code here: each different func_name has to
check that it got its expected number of arguments and perhaps raise an error.
So I refactored:
def expects(nargs: int, func_name: str, args: tuple) -> None:
if len(args) != nargs:
raise TypeError(f"Wrong arguments for {func_name}, got {len(args)}")
match func_name:
case "LEN":
expects(1, func_name, args)
return len(args[0])
case "LEFT$":
expects(2, func_name, args)
return args[0][:args[1]]
Nice. The code is tighter, easier to read, and common behavior is implemented in one place.
But the old code had an advantage: because each error condition had its own
raise line, coverage measurement could tell me whether I had tested every
func_name for the wrong number of arguments. With the error handling
happening in a helper function, that information is lost. I’ll know that
some func_name had a test for the wrong number of arguments, but
not that all of them did.
Here’s where the new idea comes in. What if I could indicate that for the
expects function, I want separate coverage data for each distinct calling
site? Then I could see that every func_name had a test for both the wrong
number of arguments and the right number of arguments. The simple branch inside
expects would be measured separately for each caller.
I have a quick proof-of-concept. A decorator on expects does the work.
Coverage.py already has dynamic contexts which are used for things like tracking
which tests called which code. The decorator starts a new context named for the
calling location, then restores the context when the function returns:
def coverage_per_caller(func):
@functools.wraps(func)
def _wrapper(*args, **kwargs):
cov = coverage.Coverage.current()
name = func.__name__
caller = inspect.currentframe().f_back
file = caller.f_code.co_filename
lineno = caller.f_lineno
prev_context = cov.switch_context(f"per_caller:{name}:{file}:{lineno}")
try:
ret = func(*args, **kwargs)
finally:
cov.switch_context(prev_context)
return ret
return _wrapper
I had to make one tiny (unreleased) change to coverage.py for this:
switch_context used to return None, but now it returns the previous
context so that we can nest them properly.
To my delight, this works! I can look at the HTML coverage report and see the
caller contexts for the lines in expects. I can see that 20 callers ran
the if line, but only 2 ran the raise, and the context names show
the file and line number of the callers for each:
This isn’t the whole solution yet. Things to improve:
- I’d like to post-process these contexts to show which callers were missing
lines inside
expects. What I’m looking for is the same kind of “this line is missing” information that I got from the original inlined logic. - These per-caller contexts overwrite the contexts we were already collecting (the test names). Ideally we’d have some kind of sub-context so that we could track both (or many) at once.
- It’s not great that I had to add a decorator to the source code. Driving this through the coverage configuration would keep these kinds of details out of the source.
But it’s a start, and gives me other ideas. I could use some aspect of the
data passed into a function as the context name. In this example, we could have
used func_name as the context instead of the caller’s location. Maybe
you have ideas for other uses.
LernerPython blog, from Reuven Lerner
Free real-world Pandas exercises, with solutions
If you want to get better at Pandas, the hard part isn’t finding tutorials. It’s finding problems worth solving. Most exercises hand you a tidy little table of five rows […]
The post Free real-world Pandas exercises, with solutions appeared first on LernerPython.

