<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: CoreMesh</title>
    <description>The latest articles on DEV Community by CoreMesh (@coremesh_team).</description>
    <link>https://dev.to/coremesh_team</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4189909%2F6f6b7e10-be86-40d7-90eb-99f21b488590.gif</url>
      <title>DEV Community: CoreMesh</title>
      <link>https://dev.to/coremesh_team</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coremesh_team"/>
    <language>en</language>
    <item>
      <title>claimcheck: Gemma 4 fact-checks my AI blog drafts against their own sources</title>
      <dc:creator>CoreMesh</dc:creator>
      <pubDate>Sat, 17 Oct 2026 07:49:37 +0000</pubDate>
      <link>https://dev.to/coremesh_team/claimcheck-gemma-4-fact-checks-my-ai-blog-drafts-against-their-own-sources-57f0</link>
      <guid>https://dev.to/coremesh_team/claimcheck-gemma-4-fact-checks-my-ai-blog-drafts-against-their-own-sources-57f0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://coremesh.online" rel="noopener noreferrer"&gt;CoreMesh&lt;/a&gt;, a small tech blog where an AI pipeline writes the first draft of every article and I approve or reject it. That is how a sentence like this got into a draft about upgrading to Python 3.15:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The stdlib now returns &lt;code&gt;frozendict&lt;/code&gt; from &lt;code&gt;EnumType.__members__&lt;/code&gt; and &lt;code&gt;errno.errorcode&lt;/code&gt; instead of &lt;code&gt;MappingProxyType&lt;/code&gt; or &lt;code&gt;dict&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It reads like a fact, but it isn't one. PEP 814 lists those two only as &lt;em&gt;possible candidates&lt;/em&gt; for &lt;code&gt;frozendict&lt;/code&gt; in the stdlib. A reader who trusted that line would write code that breaks.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/indra112f/claimcheck" rel="noopener noreferrer"&gt;claimcheck&lt;/a&gt;&lt;/strong&gt;: a small CLI that checks an AI-written draft against &lt;strong&gt;the sources the draft itself links to&lt;/strong&gt;, using an open-weight model (Gemma 4), before anything goes live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for it:&lt;/strong&gt; anyone who publishes AI-assisted writing under their own name and can't afford to be wrong in public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;solo bloggers and newsletter writers;&lt;/li&gt;
&lt;li&gt;small content agencies;&lt;/li&gt;
&lt;li&gt;docs and DevRel teams;&lt;/li&gt;
&lt;li&gt;people selling technical courses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool itself is MIT. What people would pay for is the service around it: a pre-publish check per article, a CI gate on a docs repo, or a weekly re-check of old posts as the docs they cite change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;A real run on that Python 3.15 draft, with the bad sentence put back in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 claimcheck.py examples/python315-draft.md --exclude coremesh.online
28 claims, 4 sources
  [1/28] supported    Python 3.15 uses UTF-8 as the default text encoding for files, stdio and pipes.
  ...
  [18/28] not_found    In Python 3.15, `EnumType.__members__` and `errno.errorcode` return `frozendict`.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The report puts problems first and shows the draft's words next to the source's words:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## 5. [CHECK] In Python 3.15, `EnumType.__members__` and `errno.errorcode` return `frozendict`.
&amp;gt; Draft: "The stdlib now returns `frozendict` from `EnumType.__members__` and `errno.errorcode` instead of `MappingProxyType` or `dict`"

## 7. [OK] Python 3.15 uses UTF-8 as the default text encoding for files, stdio and pipes.
&amp;gt; Draft: "Python now uses UTF-8 as the default text encoding for files, stdio and pipes"
&amp;gt; Source (https://peps.python.org/pep-0686/): "With this change, Python consistently uses UTF-8 for default encoding of files, stdio, and pipes."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The exit code is &lt;code&gt;1&lt;/code&gt; when anything needs a human, so it can block a publish step or a CI job.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/indra112f" rel="noopener noreferrer"&gt;
        indra112f
      &lt;/a&gt; / &lt;a href="https://github.com/indra112f/claimcheck" rel="noopener noreferrer"&gt;
        claimcheck
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Fact-check AI-written drafts against their own sources with an open-weight model (Gemma). Evidence quotes are verified word for word.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;claimcheck&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Fact-check an AI-written draft against &lt;strong&gt;its own sources&lt;/strong&gt; with an open-weight model, before you publish it.&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;$ claimcheck examples/python315-draft.md --exclude coremesh.online
28 claims, 4 sources
  [1/28] supported    Python 3.15 uses UTF-8 as the default text encoding for files, stdio and pipes
  [18/28] not_found    In Python 3.15, `EnumType.__members__` and `errno.errorcode` return `frozendict`.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That last line is a real mistake our blog's AI writer made: PEP 814 lists those two as &lt;em&gt;possible candidates&lt;/em&gt;, not as shipped changes. claimcheck flagged it.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Claims.&lt;/strong&gt; The model lists the checkable claims in the draft: numbers, versions, limits, defaults, dates, "X supports Y". Every claim must quote the draft. A claim whose quote isn't in the draft is dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval.&lt;/strong&gt; Every source page linked in the draft (plus any &lt;code&gt;--source&lt;/code&gt;) is fetched and split into passages. A small IDF-weighted lexical index picks the closest passages for each claim, with no…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/indra112f/claimcheck" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's one file, standard-library Python, with offline tests that use a fake model.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The pipeline has four steps. The interesting part is that &lt;strong&gt;the model is never trusted on its own word&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract claims.&lt;/strong&gt; Gemma 4 lists the checkable claims in the draft (numbers, versions, limits, defaults, "X supports Y") and has to quote the draft for each one. If the quote isn't actually in the draft, the claim is dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve.&lt;/strong&gt; Every linked source page is fetched and cut into overlapping passages. A tiny IDF-weighted lexical index picks the closest six passages per claim. There are no embeddings and no vector DB. Rare tokens like &lt;code&gt;load_module&lt;/code&gt; or &lt;code&gt;3.15&lt;/code&gt; carry the weight, which is what you want for technical claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge.&lt;/strong&gt; Gemma sees one claim and those passages only, and must answer &lt;code&gt;supported&lt;/code&gt;, &lt;code&gt;contradicted&lt;/code&gt; or &lt;code&gt;not_found&lt;/code&gt; and copy one evidence sentence word for word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the verifier.&lt;/strong&gt; This is plain code, not the model:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quote check:&lt;/strong&gt; the evidence sentence must exist in the fetched page (after normalizing quotes, dashes and whitespace). If it doesn't, the verdict is thrown away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number check:&lt;/strong&gt; every number in the draft's sentence must appear in the evidence or in the text around it. I added this after a real run marked "you'll need LLVM 21" as supported with a quote that never mentions 21.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Model and infra:&lt;/strong&gt; &lt;code&gt;gemma-4-31B-it&lt;/code&gt; on &lt;strong&gt;DigitalOcean serverless inference&lt;/strong&gt;, through the OpenAI-compatible endpoint, with &lt;code&gt;temperature: 0&lt;/code&gt;. A full article took 3 to 5 minutes and about 70k input tokens. At DigitalOcean's listed Gemma 4 serverless rate ($0.18 per 1M input tokens and $0.50 per 1M output tokens, as of October 2026), that is about &lt;strong&gt;1.4 cents per article&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I built it in one sitting with an AI agent (Claude) doing most of the typing while I steered. That fits the project: an AI wrote it, and the tests and the quote check are why I trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The unit economics only work with cheap inference.&lt;/strong&gt; A check is around 30 model calls. At about 1.4 cents per article, I can run it on every draft and every old post, every week, and still charge a few dollars a month. That's a business.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The judge shouldn't be a black box I rent.&lt;/strong&gt; A fact-checker whose model changes behind an API without notice is a liability. With open weights I can always run the exact same model myself, so the verdicts can be reproduced and audited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client drafts are private until they're published.&lt;/strong&gt; The tool talks to any OpenAI-compatible endpoint, so the same CLI can point at Gemma running locally with Ollama and the draft never leaves the machine. For an agency checking a client's unpublished piece, that's a hard requirement. I measured the numbers here on DigitalOcean only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; Swapping Gemma for another open model is one flag. If a better open checker ships next month, I switch the same day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;My first user is my own blog, so here's what it found, good and bad.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The planted mistake:&lt;/strong&gt; flagged, as shown above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A false alarm, and why the report format matters.&lt;/strong&gt; In one run it marked "all four models have a 1M-token context window" as &lt;em&gt;contradicted&lt;/em&gt;, quoting Anthropic's pricing page: "Claude 4.6 and later models (except Claude Haiku 5.5) … include the full 1M token context window at standard pricing." I checked the models overview: all four list 1M tokens. The draft was right; that sentence is about &lt;em&gt;price&lt;/em&gt;, not size. Because the report shows the draft's words next to the source's words, it took me about 30 seconds to see it. A later run left the same claim as "not found", so the model isn't fully deterministic even at temperature 0, which is one more reason a human makes the final call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My best-ranking article&lt;/strong&gt; ("Claude Code usage limit reached", page 1 on Google) got &lt;strong&gt;0 of 12 claims&lt;/strong&gt; backed by its sources. It links only to documentation &lt;strong&gt;home pages&lt;/strong&gt;. Re-run against the right support and docs pages, 3 of 10 were backed: the 5-hour and weekly limits, the limits being shared with chat, and &lt;code&gt;/usage&lt;/code&gt;. Most of the rest are my own measurements (build and review times) and a description of my own product, which no doc can confirm, and flagging them is correct. The fix there is better links, not a better model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody has paid yet. The next step is to offer a free check of one article to three bloggers who publish AI-assisted posts, and ask whether they'd pay for it weekly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: Gemma 4 is both the claim extractor and the judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of DigitalOcean&lt;/strong&gt;: it runs on DigitalOcean serverless inference.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This post and claimcheck were written with an AI agent (Claude) working under my direction. Every number above comes from real runs, and the reports are in the repo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Upgrade to Python 3.15: What Breaks and How to Fix It</title>
      <dc:creator>CoreMesh</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:35:46 +0000</pubDate>
      <link>https://dev.to/coremesh_team/upgrade-to-python-315-what-breaks-and-how-to-fix-it-32l1</link>
      <guid>https://dev.to/coremesh_team/upgrade-to-python-315-what-breaks-and-how-to-fix-it-32l1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://coremesh.online/upgrade-to-python-3-15/" rel="noopener noreferrer"&gt;CoreMesh&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Upgrading to Python 3.15 is mostly painless, with one exception. Python now uses UTF-8 as the default text encoding for files, stdio and pipes (&lt;a href="https://peps.python.org/pep-0686/" rel="noopener noreferrer"&gt;PEP 686&lt;/a&gt;). If you run on Windows, or you read data written in a legacy encoding, any &lt;code&gt;open()&lt;/code&gt; call without an explicit &lt;code&gt;encoding=&lt;/code&gt; can start raising errors or producing garbled text. Test for that first, then upgrade.&lt;/p&gt;

&lt;p&gt;The short version: on your current Python, run your test suite with &lt;code&gt;-X warn_default_encoding&lt;/code&gt; to find every implicit-encoding call and fix them, check that your dependencies support 3.15, create a fresh virtual environment on 3.15, and run the suite again. Below are the details for each step, the removed APIs that now raise instead of warn, and the new features (frozendict, lazy imports, unpacking in comprehensions) worth using once you're there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you upgrade to Python 3.15 right now
&lt;/h2&gt;

&lt;p&gt;Python 3.15.0 shipped on October 9, 2026 as a stable release. For a new project, I'd start on 3.15 without hesitation. For an existing codebase, the answer depends on two things: whether your code relies on the platform default encoding, and whether your dependencies have caught up.&lt;/p&gt;

&lt;p&gt;Two packaging changes are easy to miss. The official Windows 64-bit binaries now use the tail-calling interpreter, and the official macOS binaries now install free-threading support by default. Both are worth knowing if you file bug reports or compare performance.&lt;/p&gt;

&lt;p&gt;One platform warning from the release notes: on macOS 27.0, IDLE and other tkinter applications may hang when a menu command opens a dialog. This is an operating system behavior change that affects all current Tk versions, not something specific to 3.15. If you depend on Tk apps on a Mac, hold off on macOS 27.0 or test your workflow first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The UTF-8 default is the change most likely to bite
&lt;/h2&gt;

&lt;p&gt;Before 3.15, the default text encoding depended on the locale. Most Unix systems already use a UTF-8 locale, and Python already switched on UTF-8 mode when the locale was C or POSIX, so Linux and macOS users rarely noticed. Windows was different: the default was the legacy code page, so a bare &lt;code&gt;open("report.txt")&lt;/code&gt; read the system's ANSI code page (cp1252 on most English-language Windows installs).&lt;/p&gt;

&lt;p&gt;In 3.15, UTF-8 mode is on everywhere. The same bare &lt;code&gt;open()&lt;/code&gt; now reads UTF-8. That's the right default, and it matches Node.js, Go, Rust and Java, but if the file was written by an older tool in the legacy code page, you get one of three outcomes: a &lt;code&gt;UnicodeDecodeError&lt;/code&gt;, mojibake, or silent data corruption on round trips.&lt;/p&gt;

&lt;p&gt;Here's what the mojibake case looks like. The bytes are the same; only the decoder's assumption changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;café costs 5€&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;legacy_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cp1252&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# written by an old Windows tool
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decoded as utf-8: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;legacy_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decoded as cp1252:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;legacy_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cp1252&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to stop relying on the default at all. Three patterns cover almost every case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. You know the file is UTF-8 (JSON, TOML, Markdown, source code). Say so.
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 2. The file was written by a local tool in the system's locale encoding.
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;legacy_export.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;locale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 3. You were calling locale.getpreferredencoding() to pick an encoding.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;
&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getencoding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# locale encoding, ignores UTF-8 mode
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;encoding="locale"&lt;/code&gt; option is honored even in UTF-8 mode (that was fixed in 3.11 specifically so this upgrade could happen). And &lt;code&gt;locale.getencoding()&lt;/code&gt; exists since 3.11 for the same reason: &lt;code&gt;locale.getpreferredencoding(False)&lt;/code&gt; returns UTF-8 under UTF-8 mode, so it can no longer tell you what the system locale is.&lt;/p&gt;

&lt;p&gt;The same rule applies to subprocesses. If you call &lt;code&gt;subprocess.run(..., text=True)&lt;/code&gt; and parse the output of a Windows command-line tool, the pipe is now decoded as UTF-8. Pass &lt;code&gt;encoding="utf-8"&lt;/code&gt; or &lt;code&gt;encoding="locale"&lt;/code&gt; explicitly. PEP 686 considered using &lt;code&gt;PYTHONIOENCODING&lt;/code&gt; as the pipe default and rejected it, so there's no shortcut. If you already wrap commands with timeouts, see the &lt;a href="https://coremesh.online/python-subprocess-timeout/" rel="noopener noreferrer"&gt;subprocess timeout examples&lt;/a&gt; for the surrounding pattern.&lt;/p&gt;

&lt;p&gt;If you need time, you can turn the old behavior back on with &lt;code&gt;PYTHONUTF8=0&lt;/code&gt; in the environment or &lt;code&gt;-X utf8=0&lt;/code&gt; on the command line. Treat that as a migration aid, not a fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test for encoding problems before you upgrade
&lt;/h2&gt;

&lt;p&gt;PEP 686 spells out a procedure, and it works on the Python you're running today. The idea is to make every implicit-encoding call visible, fix it, then run under UTF-8 mode to confirm nothing changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: find every place the default encoding is used.&lt;/span&gt;
&lt;span class="c"&gt;# EncodingWarning is raised by open() and locale.getpreferredencoding().&lt;/span&gt;
python &lt;span class="nt"&gt;-X&lt;/span&gt; warn_default_encoding &lt;span class="nt"&gt;-W&lt;/span&gt; error::EncodingWarning &lt;span class="nt"&gt;-m&lt;/span&gt; pytest

&lt;span class="c"&gt;# Step 2: fix each call site with encoding="utf-8" or encoding="locale".&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: run the suite with UTF-8 mode forced on, still on your current Python.&lt;/span&gt;
&lt;span class="nv"&gt;PYTHONUTF8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turning the warning into an error is deliberate. A warning scrolls past; an error gives you a traceback at the exact &lt;code&gt;open()&lt;/code&gt; call. Run it once with &lt;code&gt;-W error&lt;/code&gt; to get the list, then fix them in bulk.&lt;/p&gt;

&lt;p&gt;Step 3 is the real preview. If the suite passes with &lt;code&gt;PYTHONUTF8=1&lt;/code&gt; on 3.14, the encoding change in 3.15 is a non-event for you. If it fails, the failures point at files or pipes that carry legacy-encoded data, and those need &lt;code&gt;encoding="locale"&lt;/code&gt; or a conversion step.&lt;/p&gt;

&lt;p&gt;On Windows, run this on a real Windows machine or CI runner. Testing only on Linux proves nothing, because Linux was already UTF-8.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removed APIs that now raise errors
&lt;/h2&gt;

&lt;p&gt;Everything in this table was deprecated earlier and is gone in 3.15. The full list is in the &lt;a href="https://docs.python.org/3.15/whatsnew/3.15.html" rel="noopener noreferrer"&gt;What's new in Python 3.15&lt;/a&gt; page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Removed or changed&lt;/th&gt;
&lt;th&gt;What to use instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AST node constructors raise &lt;code&gt;TypeError&lt;/code&gt; for missing required args or unknown keyword args&lt;/td&gt;
&lt;td&gt;Pass every required field; drop keywords that aren't fields (was a &lt;code&gt;DeprecationWarning&lt;/code&gt; since 3.13)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;datetime.strptime()&lt;/code&gt; with &lt;code&gt;%d&lt;/code&gt; but no year directive raises &lt;code&gt;ValueError&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Add a year directive to the format string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;glob.glob0()&lt;/code&gt; and &lt;code&gt;glob.glob1()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;glob.glob()&lt;/code&gt; with the &lt;code&gt;root_dir&lt;/code&gt; argument&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;http.server.CGIHTTPRequestHandler&lt;/code&gt; and the &lt;code&gt;--cgi&lt;/code&gt; flag&lt;/td&gt;
&lt;td&gt;Removed; What's new lists no direct replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loader.load_module()&lt;/code&gt; is no longer called by the import system&lt;/td&gt;
&lt;td&gt;Implement &lt;code&gt;exec_module()&lt;/code&gt; on custom loaders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;importlib.resources.files(package=...)&lt;/code&gt; keyword&lt;/td&gt;
&lt;td&gt;Pass the package positionally: &lt;code&gt;files("mypkg")&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pathlib.PurePath.is_reserved()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;os.path.isreserved()&lt;/code&gt; on Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctypes.SetPointerType()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removed, undocumented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;collections.abc.ByteString&lt;/code&gt; dropped from &lt;code&gt;__all__&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Still importable until its removal in 3.17; stop using it now&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;strptime&lt;/code&gt; one catches people who parse log lines like &lt;code&gt;"%m/%d %H:%M"&lt;/code&gt;. That worked with a warning on 3.13 and 3.14. On 3.15 it's a &lt;code&gt;ValueError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The custom-loader change is easy to miss because it lives in code you rarely touch: the import system no longer calls &lt;code&gt;load_module()&lt;/code&gt;, so any custom loader that only implements the old method needs an &lt;code&gt;exec_module()&lt;/code&gt; before you upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  New syntax and built-ins you can start using
&lt;/h2&gt;

&lt;p&gt;Once the suite is green, these four features are the ones most likely to change how you write day-to-day code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;frozendict&lt;/strong&gt; is a new built-in immutable mapping (&lt;a href="https://peps.python.org/pep-0814/" rel="noopener noreferrer"&gt;PEP 814&lt;/a&gt;). It's not a &lt;code&gt;dict&lt;/code&gt; subclass; it inherits from &lt;code&gt;object&lt;/code&gt; and implements the &lt;code&gt;Mapping&lt;/code&gt; protocol. It preserves insertion order, pickles, and is hashable when its values are hashable, which makes it usable as a dict key, a set member, a safe function default, and an argument to &lt;code&gt;functools.lru_cache()&lt;/code&gt;-decorated functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DEFAULTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULTS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;      &lt;span class="c1"&gt;# safe default, no mutable-default bug
&lt;/span&gt;    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;DEFAULTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;       &lt;span class="c1"&gt;# merge into a new dict
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;                     &lt;span class="c1"&gt;# from a dict
&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;         &lt;span class="c1"&gt;# from key/value pairs
&lt;/span&gt;&lt;span class="nf"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                &lt;span class="c1"&gt;# both
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Construction from a dict is a shallow O(n) copy. &lt;code&gt;frozendict.copy()&lt;/code&gt; returns the same object in CPython, and mutable values stay mutable, so use &lt;code&gt;copy.deepcopy()&lt;/code&gt; if you need isolation. &lt;code&gt;eval()&lt;/code&gt; and &lt;code&gt;exec()&lt;/code&gt; accept a &lt;code&gt;frozendict&lt;/code&gt; for &lt;code&gt;globals&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sentinel&lt;/strong&gt; is also a new built-in type (PEP 661), listed in the release highlights. The release notes don't spell out the API in the summary, so check the What's new page before using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit lazy imports&lt;/strong&gt; add a &lt;code&gt;lazy&lt;/code&gt; soft keyword (&lt;a href="https://peps.python.org/pep-0810/" rel="noopener noreferrer"&gt;PEP 810&lt;/a&gt;). The module is loaded on first use instead of at the import statement, which cuts startup time for CLIs and tools that import heavy libraries they only sometimes need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pretty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# json is loaded here, not above
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to know. First, &lt;code&gt;lazy from module import Class&lt;/code&gt; still executes the whole module on first access; there's no way to run part of a file. Second, for code that must also run on older versions, use the &lt;code&gt;__lazy_modules__&lt;/code&gt; global, which 3.15 honors and older versions ignore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Lazy on 3.15+, eager on older Python
&lt;/span&gt;&lt;span class="n"&gt;__lazy_modules__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decimal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Unpacking in comprehensions&lt;/strong&gt; (PEP 798) lets you write &lt;code&gt;[*xs for xs in items]&lt;/code&gt; where before you needed a double loop or &lt;code&gt;itertools.chain.from_iterable&lt;/code&gt;. It works in list, set and dict comprehensions and in generator expressions, with &lt;code&gt;*&lt;/code&gt; for iterables and &lt;code&gt;**&lt;/code&gt; for mappings. Unpacking is restricted to the top level of the expression. This example runs on any Python 3 and uses the new syntax when it's available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;nested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[*xs for xs in nested]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nested&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PEP 798 syntax&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;xs&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nested&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;double loop (Python %d.%d has no PEP 798)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;via&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One readability trap: &lt;code&gt;f(*x for x in y)&lt;/code&gt; means &lt;code&gt;f((*x for x in y))&lt;/code&gt;, a single generator argument, not unpacked arguments. Add the parentheses yourself so the next reader doesn't have to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the experimental JIT does and doesn't give you
&lt;/h2&gt;

&lt;p&gt;The JIT is still experimental and is not on in a default build. Per the What's new page, the pyperformance suite reports a 7-8% geometric mean improvement for the JIT over the standard interpreter on x86-64 Linux, and an 11-12% speedup over the tail-calling interpreter on AArch64 macOS. The spread across individual benchmarks goes from roughly a 15% slowdown to over 100% speedup, so your workload may land anywhere in that range.&lt;/p&gt;

&lt;p&gt;The practical upshot: don't upgrade for the JIT. If you build CPython yourself with the JIT enabled, you'll need LLVM 21 at build time (end users running the resulting binary don't need LLVM). The new tracing frontend records real execution paths and handles object creation, overloaded operations and generators partially, which is why more code speeds up than in 3.14. GDB and GNU &lt;code&gt;backtrace()&lt;/code&gt; can now unwind through JIT frames on supported Linux ELF platforms, so crash reports from JIT builds are no longer dead ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install Python 3.15 on Windows, macOS and Linux
&lt;/h2&gt;

&lt;p&gt;Installers come from the python.org download page for the 3.15.0 release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows.&lt;/strong&gt; The download page points to the Python install manager, and the Windows release manifest is installed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;3.15&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traditional installers also exist: 64-bit (recommended), 32-bit and ARM64, plus embeddable packages for each if you're bundling Python into an application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS.&lt;/strong&gt; The macOS installer supports macOS 10.15 and later and, new in 3.15, installs free-threading support by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux (including Ubuntu).&lt;/strong&gt; The download page lists source tarballs (gzipped and XZ compressed) but no Linux binaries. Your options are your distribution's packages once they ship 3.15, a tool that fetches prebuilt interpreters, or building from the tarball. The release announcement itself shows uv in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx &lt;span class="nt"&gt;--python&lt;/span&gt; 3.15 whatsnewt   &lt;span class="c"&gt;# a TUI tour of the new features, run on 3.15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whichever route you take on Linux, don't &lt;code&gt;pip install&lt;/code&gt; into the system interpreter. If you hit the externally-managed-environment error, the &lt;a href="https://coremesh.online/externally-managed-environment-pip/" rel="noopener noreferrer"&gt;safe fix for that pip error&lt;/a&gt; is a virtual environment, which is where the upgrade should live anyway.&lt;/p&gt;

&lt;p&gt;After installing, create a clean environment with the new interpreter rather than pointing an old one at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3.15 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need a refresher on activation across shells, see &lt;a href="https://coremesh.online/python-virtual-environment/" rel="noopener noreferrer"&gt;how to create and activate a Python virtual environment&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes when upgrading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Setting &lt;code&gt;PYTHONUTF8=0&lt;/code&gt; and forgetting it.&lt;/strong&gt; It silences the problem on one machine and moves the crash to CI or a colleague's laptop. Use it only while you fix call sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming &lt;code&gt;encoding="locale"&lt;/code&gt; means UTF-8.&lt;/strong&gt; It means the system locale encoding, which on Windows is the legacy code page. Use it only for files that really were written that way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing only on Linux.&lt;/strong&gt; Linux was already UTF-8, so a green Linux run tells you nothing about Windows behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expecting &lt;code&gt;frozendict.copy()&lt;/code&gt; to isolate mutable values.&lt;/strong&gt; It returns the same object. Reach for &lt;code&gt;copy.deepcopy()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Making a module lazy when its import has side effects.&lt;/strong&gt; Registration, logging setup or monkeypatching that used to happen at startup now happens on first use, or never. Keep those imports eager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsing dates with &lt;code&gt;%d&lt;/code&gt; and no year.&lt;/strong&gt; This is the removal most likely to appear in a production traceback, because it was only a warning before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusing the old virtual environment.&lt;/strong&gt; Compiled extensions built for 3.14 won't load. Build a new one and reinstall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expecting a JIT speedup from the stock installer.&lt;/strong&gt; The JIT is experimental and the reported gains are for JIT-enabled builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your code is async-heavy, the same rules apply inside coroutines; nothing in the UTF-8 change is specific to asyncio, but file and pipe reads in &lt;a href="https://coremesh.online/python-async-await/" rel="noopener noreferrer"&gt;async code&lt;/a&gt; are just as affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;On your current Python, run the suite with &lt;code&gt;-X warn_default_encoding -W error::EncodingWarning&lt;/code&gt; and fix every reported call with an explicit &lt;code&gt;encoding=&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the suite again with &lt;code&gt;PYTHONUTF8=1&lt;/code&gt; set. Do this on Windows if you ship to Windows.&lt;/li&gt;
&lt;li&gt;Grep for &lt;code&gt;strptime&lt;/code&gt;, &lt;code&gt;glob0&lt;/code&gt;, &lt;code&gt;glob1&lt;/code&gt;, &lt;code&gt;load_module&lt;/code&gt;, &lt;code&gt;is_reserved&lt;/code&gt;, &lt;code&gt;CGIHTTPRequestHandler&lt;/code&gt; and &lt;code&gt;ByteString&lt;/code&gt; and replace them using the table above.&lt;/li&gt;
&lt;li&gt;Check that each dependency with compiled extensions publishes a 3.15 build.&lt;/li&gt;
&lt;li&gt;Install 3.15 and create a fresh virtual environment with &lt;code&gt;python3.15 -m venv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install your project into it and run the full suite.&lt;/li&gt;
&lt;li&gt;Only then adopt new features: &lt;code&gt;frozendict&lt;/code&gt; for constants and defaults, &lt;code&gt;lazy import&lt;/code&gt; for slow optional imports, &lt;code&gt;[*xs for xs in items]&lt;/code&gt; for flattening.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;PYTHONUTF8=0&lt;/code&gt; out of your production configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I install Python 3.15 on Ubuntu or another Linux distribution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The python.org download page provides source tarballs but no Linux binaries. Use your distribution's packages when they ship 3.15, a version manager that fetches prebuilt interpreters such as uv, or build from the tarball, and then put your project in a virtual environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I install Python 3.15 on Windows?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the Python install manager and run py install 3.15, or download the 64-bit installer, which is the recommended one. The official 64-bit Windows binaries now use the tail-calling interpreter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I install Python 3.15 on a Mac?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Download the macOS installer, which supports macOS 10.15 and later and installs free-threading support by default. If you use IDLE or other tkinter apps, note that macOS 27.0 has a Tk dialog hang that affects every Python version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I update to Python 3.15?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes for new projects. For existing code, first run your tests with -X warn_default_encoding and then with PYTHONUTF8=1 on your current Python; if both pass and your dependencies support 3.15, upgrading is low risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Python 3.15 faster?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The experimental JIT shows a 7-8% geometric mean improvement on x86-64 Linux and 11-12% on AArch64 macOS in the pyperformance suite, but individual results range from roughly a 15% slowdown to over 100% speedup and the JIT is not enabled in default builds.&lt;/p&gt;

&lt;p&gt;The original, with any later corrections, is on &lt;a href="https://coremesh.online/upgrade-to-python-3-15/" rel="noopener noreferrer"&gt;CoreMesh&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was drafted by an AI system, fact-checked against the official sources linked above, and reviewed by a human before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>python315</category>
      <category>upgrade</category>
    </item>
    <item>
      <title>Claude Opus 5.5 Fast Mode: Enable It With Request Examples (No Beta Header Needed)</title>
      <dc:creator>CoreMesh</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:18:48 +0000</pubDate>
      <link>https://dev.to/coremesh_team/claude-opus-55-fast-mode-enable-it-with-request-examples-no-beta-header-needed-36pp</link>
      <guid>https://dev.to/coremesh_team/claude-opus-55-fast-mode-enable-it-with-request-examples-no-beta-header-needed-36pp</guid>
      <description>&lt;p&gt;Fast mode is officially out of research preview for &lt;strong&gt;Claude Opus 5.5&lt;/strong&gt;. You no longer need to join a waitlist or pass the old beta header to get faster token streaming.&lt;/p&gt;

&lt;p&gt;Here is the minimal setup, what changed in the October 2026 release notes, pricing details, and the breaking parameter gotchas to watch out for when migrating older Opus requests.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 *&lt;em&gt;Originally published on &lt;a href="https://coremesh.online/claude-opus-5-5-fast-mode" rel="noopener noreferrer"&gt;CoreMesh&lt;/a&gt; *&lt;/em&gt; — &lt;em&gt;Practical AI and coding guides, tested fixes, and runnable prompts.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Smallest Request That Turns On Fast Mode
&lt;/h2&gt;

&lt;p&gt;The request body is a standard Messages API payload with one extra field, &lt;code&gt;"speed": "fast"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-opus-5-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"speed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize this stack trace in two sentences."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Three things to check in that body:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;model&lt;/code&gt; is &lt;code&gt;claude-opus-5-5&lt;/code&gt;&lt;/strong&gt;, the official Claude API ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;speed&lt;/code&gt; is &lt;code&gt;"fast"&lt;/code&gt;&lt;/strong&gt;. Leave the field out and you get standard speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is no &lt;code&gt;thinking&lt;/code&gt; field.&lt;/strong&gt; Passing older &lt;code&gt;thinking&lt;/code&gt; parameters on Opus 5.5 will trigger a &lt;code&gt;400&lt;/code&gt; error (see the gotchas section below).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What Changed Recently
&lt;/h2&gt;

&lt;p&gt;The latest release notes bundle several updates for Opus 5.5 fast mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No waitlist:&lt;/strong&gt; Fast mode is out of research preview for Claude Opus 5.5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No beta header required:&lt;/strong&gt; Requests to Opus 5.5 no longer require the &lt;code&gt;fast-mode-2026-02-01&lt;/code&gt; beta header (though requests that still send it will keep working). &lt;em&gt;Note: If you are still calling &lt;code&gt;claude-opus-5&lt;/code&gt; or &lt;code&gt;claude-opus-4-8&lt;/code&gt;, keep sending the beta header.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default rate limits:&lt;/strong&gt; Opus 5.5 now has a default fast mode rate limit across the Start, Build, and Scale usage tiers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure support:&lt;/strong&gt; Available on the first-party Claude API and on Claude in Microsoft Foundry for deployments hosted on Azure (not yet available on AWS/Bedrock or Batch API).&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Pricing &amp;amp; When to Use It
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opus 5.5 Fast Mode:&lt;/strong&gt; &lt;code&gt;$8 USD / MTok&lt;/code&gt; (Input) · &lt;code&gt;$40 USD / MTok&lt;/code&gt; (Output)&lt;/li&gt;
&lt;li&gt;Fast mode pricing applies across the full context window (including requests over 200k input tokens), and prompt caching or data residency multipliers apply on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule of thumb:&lt;/strong&gt; Since fast mode is not available with the Batch API, save &lt;code&gt;"speed": "fast"&lt;/code&gt; for interactive paths where a user is actively watching tokens stream in, and route offline jobs to standard/batch.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Gotchas When Migrating Old Requests
&lt;/h2&gt;

&lt;p&gt;Opus 5.5 introduces behavior changes unrelated to fast mode that often break copied-over requests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;thinking&lt;/code&gt; parameter returns a 400 error:&lt;/strong&gt; Both &lt;code&gt;thinking: {"type": "disabled"}&lt;/code&gt; and &lt;code&gt;thinking: {"type": "enabled", ...}&lt;/code&gt; will fail. Omit &lt;code&gt;thinking&lt;/code&gt; entirely and use the &lt;strong&gt;&lt;code&gt;effort&lt;/code&gt;&lt;/strong&gt; parameter (&lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;) to control reasoning depth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tool_choice&lt;/code&gt; types &lt;code&gt;any&lt;/code&gt; and &lt;code&gt;tool&lt;/code&gt; return a 400 error:&lt;/strong&gt; Use &lt;code&gt;auto&lt;/code&gt; with strict tool use instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computer use toolset version:&lt;/strong&gt; On the Claude API and Google Cloud, computer use now requires &lt;code&gt;computer_toolset_20260801&lt;/code&gt; (the older &lt;code&gt;computer_20251124&lt;/code&gt; returns a 400).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Read the full breakdown
&lt;/h2&gt;

&lt;p&gt;The complete side-by-side curl/SDK examples, the model comparison table and the rate limit checklist are in the original article on CoreMesh: &lt;a href="https://coremesh.online/claude-opus-5-5-fast-mode/" rel="noopener noreferrer"&gt;Claude Opus 5.5 Fast Mode&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was drafted by an AI system. Facts were checked against Anthropic's official documentation, which the original article lists as sources, and a human reviewed it before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
