<?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: Dan Lumatic</title>
    <description>The latest articles on DEV Community by Dan Lumatic (@dan_lumatic).</description>
    <link>https://dev.to/dan_lumatic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4186289%2F9e101898-a70f-457f-82d0-b79095daef95.png</url>
      <title>DEV Community: Dan Lumatic</title>
      <link>https://dev.to/dan_lumatic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dan_lumatic"/>
    <language>en</language>
    <item>
      <title>JSON to Table in Python: 10 Working Recipes</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Mon, 09 Nov 2026 04:08:29 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/json-to-table-in-python-10-working-recipes-214b</link>
      <guid>https://dev.to/dan_lumatic/json-to-table-in-python-10-working-recipes-214b</guid>
      <description>&lt;p&gt;You called &lt;code&gt;pd.json_normalize&lt;/code&gt; on an API response. One line, a DataFrame, done. Then you looked at the &lt;code&gt;items&lt;/code&gt; column and found a whole list of dicts crammed into each cell.&lt;/p&gt;

&lt;p&gt;Nested objects flatten on their own, but a JSON array to table conversion needs a row shape you choose, and that choice decides whether the table is right.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Join each array into one cell when a person will read the table. Use one row per item (&lt;code&gt;record_path&lt;/code&gt; or &lt;code&gt;explode&lt;/code&gt;) when you analyze. If unsure, use &lt;code&gt;explode&lt;/code&gt;, because it never loses a parent.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;record_path&lt;/code&gt; silently drops any parent whose list is empty. Deeper nesting takes a list path, dicts keyed by id take &lt;code&gt;DataFrame.from_dict(..., orient="index")&lt;/code&gt;, and &lt;code&gt;max_level&lt;/code&gt; stops flattening early.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;to_html&lt;/code&gt; escapes cell text. &lt;code&gt;to_markdown&lt;/code&gt; needs &lt;code&gt;tabulate&lt;/code&gt;, leaves &lt;code&gt;|&lt;/code&gt; unescaped and prints missing values as &lt;code&gt;nan&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every pandas JSON to table recipe below ran on Python 3.11.16, pandas 3.1.1 and tabulate 0.10.0, and each output block was copied from a real run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which shape should each array take?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Join the array into one cell when a person will read the table. Use one row per item when someone will sum, count or group by item.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are unsure, use &lt;code&gt;explode&lt;/code&gt; (Recipe 3): it never loses a parent. The recipes run on three orders (the data is in Recipe 1). Order 1003 has an empty &lt;code&gt;items&lt;/code&gt; list, which is what real APIs send for "nothing yet". The three array recipes treat it differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recipe&lt;/th&gt;
&lt;th&gt;Rows from our 3 orders&lt;/th&gt;
&lt;th&gt;Order 1003&lt;/th&gt;
&lt;th&gt;Pick it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2: &lt;code&gt;record_path&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3, one per line item&lt;/td&gt;
&lt;td&gt;dropped&lt;/td&gt;
&lt;td&gt;you analyze line items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3: &lt;code&gt;explode&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4, one per line item plus one for 1003&lt;/td&gt;
&lt;td&gt;kept, with empty sku and qty&lt;/td&gt;
&lt;td&gt;you analyze line items and still need every order counted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4: joined string&lt;/td&gt;
&lt;td&gt;3, one per order&lt;/td&gt;
&lt;td&gt;kept, with an empty items cell&lt;/td&gt;
&lt;td&gt;a person will read the table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Recipe 1: nested objects flatten into dotted columns
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pd.json_normalize&lt;/code&gt; names each nested key with a dot between the levels (&lt;a href="https://pandas.pydata.org/docs/reference/api/pandas.json_normalize.html" rel="noopener noreferrer"&gt;pandas docs&lt;/a&gt;). It wants parsed Python objects, so run &lt;code&gt;json.load&lt;/code&gt; or &lt;code&gt;json.loads&lt;/code&gt; first: a raw string raises &lt;code&gt;NotImplementedError&lt;/code&gt; with an empty message.&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&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;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&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;name&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;Ada&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;city&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;London&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;items&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;sku&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;A1&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;qty&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&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;B2&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;qty&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="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&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;name&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;Linus&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;city&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;Helsinki&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;items&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;sku&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;A1&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;qty&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1003&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&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;name&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;Grace&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;city&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;New York&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;items&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&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;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   order_id                                               items customer.name customer.city
0      1001  [{'sku': 'A1', 'qty': 2}, {'sku': 'B2', 'qty': 1}]           Ada        London
1      1002                           [{'sku': 'A1', 'qty': 5}]         Linus      Helsinki
2      1003                                                  []         Grace      New York
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;customer.name&lt;/code&gt; and &lt;code&gt;customer.city&lt;/code&gt; came from the nested object. &lt;code&gt;items&lt;/code&gt; kept its list. Pass &lt;code&gt;sep="_"&lt;/code&gt; to get &lt;code&gt;customer_name&lt;/code&gt;, which saves you from bracket access like &lt;code&gt;df["customer.name"]&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; list the array columns in your own file before you pick a recipe. If it prints &lt;code&gt;[]&lt;/code&gt;, Recipe 1 is all you need. If it prints &lt;code&gt;['data']&lt;/code&gt;, the whole response sits inside an envelope: pass &lt;code&gt;record_path="data"&lt;/code&gt; to get one row per record.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&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;orders.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="c1"&gt;# your file here
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;load&lt;/span&gt;&lt;span class="p"&gt;(&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;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&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;list_cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;any&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;list_cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On our orders file this prints:&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;items&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;h2&gt;
  
  
  Recipes 2 to 4: turn the array column into rows or a cell
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Recipe 2: child rows with &lt;code&gt;record_path&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Name the array, and list the parent fields you want carried along in &lt;code&gt;meta&lt;/code&gt;. A nested parent field takes a list path.&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;# orders: the list from Recipe 1
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;record_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;meta&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;order_id&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;customer&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;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  sku  qty order_id customer_name
0  A1    2     1001           Ada
1  B2    1     1001           Ada
2  A1    5     1002         Linus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order 1003 is gone: &lt;code&gt;record_path&lt;/code&gt; builds rows from the items, so an order without items produces no rows, and nothing warns you. If you count orders from this frame, you undercount. The row-count check at the end of this page catches it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recipe 3: exploded rows with &lt;code&gt;explode&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Normalize the whole thing first, give every list element its own row, then normalize the exploded column.&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;# orders: the list from Recipe 1
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&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;flat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&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;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Int64&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="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;   &lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt; &lt;span class="k"&gt;customer&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt; &lt;span class="k"&gt;customer&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;city&lt;/span&gt;  &lt;span class="k"&gt;sku&lt;/span&gt;   &lt;span class="k"&gt;qty&lt;/span&gt;
&lt;span class="mf"&gt;0&lt;/span&gt;      &lt;span class="mf"&gt;1001&lt;/span&gt;           &lt;span class="k"&gt;Ada&lt;/span&gt;        &lt;span class="k"&gt;London&lt;/span&gt;   &lt;span class="k"&gt;A&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;     &lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="mf"&gt;1&lt;/span&gt;      &lt;span class="mf"&gt;1001&lt;/span&gt;           &lt;span class="k"&gt;Ada&lt;/span&gt;        &lt;span class="k"&gt;London&lt;/span&gt;   &lt;span class="k"&gt;B&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;     &lt;span class="mf"&gt;1&lt;/span&gt;
&lt;span class="mf"&gt;2&lt;/span&gt;      &lt;span class="mf"&gt;1002&lt;/span&gt;         &lt;span class="k"&gt;Linus&lt;/span&gt;      &lt;span class="k"&gt;Helsinki&lt;/span&gt;   &lt;span class="k"&gt;A&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;     &lt;span class="mf"&gt;5&lt;/span&gt;
&lt;span class="mf"&gt;3&lt;/span&gt;      &lt;span class="mf"&gt;1003&lt;/span&gt;         &lt;span class="k"&gt;Grace&lt;/span&gt;      &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="k"&gt;York&lt;/span&gt;  &lt;span class="k"&gt;NaN&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;NA&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order 1003 survives as a row of empty cells. That row is also why the &lt;code&gt;astype("Int64")&lt;/code&gt; line exists: without it, the missing quantity turns &lt;code&gt;qty&lt;/code&gt; into floats (&lt;code&gt;2.0&lt;/code&gt;, &lt;code&gt;1.0&lt;/code&gt;, &lt;code&gt;5.0&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Recipe 4: one joined cell per order
&lt;/h3&gt;

&lt;p&gt;Collapse each array into a string and keep one row per parent.&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;# orders: the list from Recipe 1
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&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;items&lt;/span&gt;&lt;span class="sh"&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;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;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&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="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="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   order_id         items customer_name customer_city
0      1001  A1 x2, B2 x1           Ada        London
1      1002         A1 x5         Linus      Helsinki
2      1003                       Grace      New York
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTML and Markdown tables are for eyes, so Recipe 8 starts from this joined frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipes 5 to 7: deeper nesting
&lt;/h2&gt;

&lt;p&gt;Real payloads go past one level. These three recipes cover arrays inside arrays, dicts keyed by id, and objects you want to leave unflattened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recipe 5: arrays inside arrays, and arrays of scalars
&lt;/h3&gt;

&lt;p&gt;Here each order has a list of tags (scalars) and shipments that each hold their own boxes. A list path walks arrays inside arrays, and &lt;code&gt;meta&lt;/code&gt; can reach a field of the parent array, here the shipment id.&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;deep&lt;/span&gt; &lt;span class="o"&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;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&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;gift&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;rush&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;shipments&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&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;S1&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;boxes&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;sku&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;A1&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;qty&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&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;B2&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;qty&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;id&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;S2&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;boxes&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;sku&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;A1&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;qty&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="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;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&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;shipments&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="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;boxes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record_path&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;shipments&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;boxes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;meta&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;order_id&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;shipments&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;id&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boxes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&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;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&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;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&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;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  sku  qty order_id shipments.id
0  A1    2     1001           S1
1  B2    1     1001           S1
2  A1    1     1001           S2
   order_id        tags
0      1001  gift, rush
1      1002            
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order 1002 has no shipments, so it vanishes from the box rows just as 1003 did in Recipe 2. An array of scalars needs no new recipe: &lt;code&gt;", ".join&lt;/code&gt; makes it a cell, the joined shape one level down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recipe 6: a dict keyed by id
&lt;/h3&gt;

&lt;p&gt;A dict keyed by id is not a list of records, so &lt;code&gt;json_normalize&lt;/code&gt; gives you one wide row. &lt;code&gt;DataFrame.from_dict&lt;/code&gt; with &lt;code&gt;orient="index"&lt;/code&gt; gives one row per key:&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;users&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;u1&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;name&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;Ada&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;plan&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;pro&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;u2&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;name&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;Linus&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;plan&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;free&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="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_string&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  u1.name u1.plan u2.name u2.plan
0     Ada     pro   Linus    free
     name  plan
u1    Ada   pro
u2  Linus  free
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recipe 7: stop flattening early with &lt;code&gt;max_level&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Objects flatten all the way down by default. &lt;code&gt;max_level&lt;/code&gt; stops early and leaves the rest as a dict in the cell:&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;cust&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;order_id&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&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;name&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;Ada&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;address&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;city&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;London&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;geo&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;lat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;51.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cust&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_string&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json_normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cust&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   order_id customer.name customer.address.city  customer.address.geo.lat  customer.address.geo.lon
0         1           Ada                London                      51.5                      -0.1
   order_id customer.name                                       customer.address
0         1           Ada  {'city': 'London', 'geo': {'lat': 51.5, 'lon': -0.1}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recipe 8: &lt;code&gt;to_html&lt;/code&gt; escapes cell text for you
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DataFrame.to_html&lt;/code&gt; returns a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; string and escapes &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;amp;&lt;/code&gt; by default, so a stray tag in your data cannot break the page. &lt;code&gt;border=0&lt;/code&gt; drops the default &lt;code&gt;border="1"&lt;/code&gt; attribute and &lt;code&gt;classes&lt;/code&gt; adds a CSS class. Here are orders 1002 and 1003 from the joined frame:&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;# flat: the joined frame from Recipe 4
&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="n"&gt;iloc&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&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;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dataframe orders"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"text-align: right;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;order_id&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;items&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;1002&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;A1 x5&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;1003&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order 1003 is a row with an empty cell, so the count stays right. Escaping, on a cell that holds &lt;code&gt;&amp;lt;b&amp;gt;hi&amp;lt;/b&amp;gt; &amp;amp; bye&lt;/code&gt;:&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&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;&amp;lt;b&amp;gt;hi&amp;lt;/b&amp;gt; &amp;amp; bye&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;escape&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;escape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True &amp;lt;td&amp;gt;&amp;amp;lt;b&amp;amp;gt;hi&amp;amp;lt;/b&amp;amp;gt; &amp;amp;amp; bye&amp;lt;/td&amp;gt;
False &amp;lt;td&amp;gt;&amp;lt;b&amp;gt;hi&amp;lt;/b&amp;gt; &amp;amp; bye&amp;lt;/td&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;escape=False&lt;/code&gt; only for HTML you wrote yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 9: &lt;code&gt;to_markdown&lt;/code&gt; needs tabulate, and it mangles pipes and missing values
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DataFrame.to_markdown&lt;/code&gt; prints a pipe table you can paste into a README or a pull request (&lt;a href="https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_markdown.html" rel="noopener noreferrer"&gt;pandas docs&lt;/a&gt;). It depends on the optional &lt;code&gt;tabulate&lt;/code&gt; package. Without it you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ImportError: `Import tabulate` failed.  Use pip, conda, or your preferred package management tool to install the tabulate package.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;pip install tabulate&lt;/code&gt; it works, but real text has two traps, and pandas guards against neither:&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&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;sku&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;A1&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;note&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;fits a|b&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;sku&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;B2&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;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&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="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| sku   | note     |
|:------|:---------|
| A1    | fits a|b |
| B2    | nan      |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first row now has three cells under a two-column header, because the pipe inside &lt;code&gt;a|b&lt;/code&gt; splits the cell. The second row says &lt;code&gt;nan&lt;/code&gt; where the note was empty. Fill the gaps and escape the pipes before you render:&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&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;sku&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;A1&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;note&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;fits a|b&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;sku&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;B2&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;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notna&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&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="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;|&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="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| sku   | note      |
|:------|:----------|
| A1    | fits a&lt;span class="se"&gt;\|&lt;/span&gt;b |
| B2    |           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recipe 10: HTML and Markdown tables with the standard library only
&lt;/h2&gt;

&lt;p&gt;Skip this one if pandas is installed. When you cannot install it (a Lambda layer, a build script, a pre-commit hook), &lt;code&gt;json&lt;/code&gt; and &lt;code&gt;html&lt;/code&gt; are enough. This module flattens nested objects into dotted keys and builds the header from the union of every record's keys. Its &lt;code&gt;list_cell&lt;/code&gt; argument decides what an array becomes, and the default is compact JSON. Pass a function that joins &lt;code&gt;sku x qty&lt;/code&gt; strings to get the joined shape of Recipe 4.&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;# orders: the list from Recipe 1
&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;html&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&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;xs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&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;,&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;:&lt;/span&gt;&lt;span class="sh"&gt;"&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;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_cell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_cell&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;table_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_cell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;compact&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_cell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# union of keys
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cols&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="nf"&gt;table_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;table&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cols&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="nf"&gt;table_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&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;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&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="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;lines&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;| &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; |&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;| &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; |&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;| &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; |&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&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="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&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="nf"&gt;to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&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;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coupon&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;SPRING|5&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;order_id&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;customer.name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;customer.city&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;items&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;1003&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;Grace&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;New York&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;[]&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
| order_id | customer.name | customer.city | items | coupon |
| --- | --- | --- | --- | --- |
| 1001 | Ada | London | [{"sku":"A1","qty":2},{"sku":"B2","qty":1}] |  |
| 1002 | Linus | Helsinki | [{"sku":"A1","qty":5}] |  |
| 1003 | Grace | New York | [] |  |
| 1004 |  |  |  | SPRING\|5 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order 1003 shows up as &lt;code&gt;[]&lt;/code&gt;, so no row is lost. Building the header from the first record alone would have dropped &lt;code&gt;coupon&lt;/code&gt;, which exists only on order 1004.&lt;/p&gt;

&lt;h2&gt;
  
  
  The errors you will actually hit
&lt;/h2&gt;

&lt;p&gt;Every message below is copied from a real run on pandas 3.1.1.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;KeyError: "Key 'rows' not found. If specifying a record_path, all elements of data should have the path."&lt;/code&gt; means some record lacks the &lt;code&gt;record_path&lt;/code&gt; key, including a record with no &lt;code&gt;items&lt;/code&gt; key at all (an empty list is fine). Use Recipe 3 instead: a missing key becomes a row of empty cells.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KeyError: "Key 'customer' not found. To replace missing values of 'customer' with np.nan, pass in errors='ignore'"&lt;/code&gt; means a record lacks a &lt;code&gt;meta&lt;/code&gt; field. &lt;code&gt;errors="ignore"&lt;/code&gt; fills those cells with &lt;code&gt;NaN&lt;/code&gt;. The cost: a mistyped &lt;code&gt;meta&lt;/code&gt; key now gives a column of &lt;code&gt;NaN&lt;/code&gt; where you used to get this error.&lt;/li&gt;
&lt;li&gt;No error, one row: if the payload is &lt;code&gt;{"status": "ok", "data": [...]}&lt;/code&gt;, &lt;code&gt;pd.json_normalize(payload)&lt;/code&gt; returns a single row whose &lt;code&gt;data&lt;/code&gt; cell holds the whole list. Pass &lt;code&gt;record_path="data"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FileNotFoundError: [Errno 2] No such file or directory: '[{"id": 1, "name": "Ada"}, {"id": 2, "name": "Linus"}]'&lt;/code&gt; comes from &lt;code&gt;pd.read_json&lt;/code&gt; on a JSON string, which pandas reads as a file path. Pass a path, or wrap the text in &lt;code&gt;io.StringIO(raw)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ValueError: Trailing data at position 24&lt;/code&gt; means the file is NDJSON, one object per line. Add &lt;code&gt;lines=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 25)&lt;/code&gt; is &lt;code&gt;json.loads&lt;/code&gt; on that same NDJSON text. Parse line by line: &lt;code&gt;[json.loads(line) for line in text.splitlines()]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ValueError: If using all scalar values, you must pass an index&lt;/code&gt; comes from &lt;code&gt;pd.read_json&lt;/code&gt; or &lt;code&gt;pd.DataFrame&lt;/code&gt; on a single object. &lt;code&gt;pd.json_normalize(obj)&lt;/code&gt; accepts a lone dict and returns one row.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A display gotcha, not an error: &lt;code&gt;print(df)&lt;/code&gt; collapses the middle columns when the frame is wider than your terminal and shows &lt;code&gt;order_id  ... customer.city&lt;/code&gt;. Nothing is lost. Use &lt;code&gt;print(df.to_string())&lt;/code&gt;, as the blocks above do.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I convert nested JSON to a table in Python?
&lt;/h2&gt;

&lt;p&gt;Parse it with &lt;code&gt;json.loads&lt;/code&gt; (or &lt;code&gt;json.load&lt;/code&gt; for a file) and pass the result to &lt;code&gt;pd.json_normalize&lt;/code&gt;. Nested objects become dotted columns and arrays stay as lists in a cell, as the output in Recipe 1 shows. Use Recipe 2 or 3 for one row per array element.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I turn JSON into a Markdown table in Python?
&lt;/h2&gt;

&lt;p&gt;Flatten with &lt;code&gt;pd.json_normalize&lt;/code&gt;, join the arrays, fill missing values, escape &lt;code&gt;|&lt;/code&gt; and call &lt;code&gt;df.to_markdown(index=False)&lt;/code&gt; after &lt;code&gt;pip install tabulate&lt;/code&gt; (Recipe 9). Without pandas, the &lt;code&gt;to_markdown&lt;/code&gt; function in Recipe 10 does the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I flatten JSON that contains arrays?
&lt;/h2&gt;

&lt;p&gt;Pick a shape from the table at the top: &lt;code&gt;record_path&lt;/code&gt; or &lt;code&gt;explode&lt;/code&gt; for one row per element, a joined string for one row per parent. Arrays inside arrays take a list path, as in Recipe 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you only need the table once
&lt;/h2&gt;

&lt;p&gt;If you have a single file and no script to maintain, a client-side converter is enough: one that runs entirely in your browser, so nothing is uploaded. Before you trust one, check three things. It keeps each object as one row and each array in its cell as compact JSON, so the row count matches your record count and no empty array disappears. It flattens nested objects into dotted columns, as &lt;code&gt;json_normalize&lt;/code&gt; does. Its Markdown view is ready to copy. Most converters do not explode line items into child rows. For one row per item, use Recipe 2 or 3 above.&lt;/p&gt;

&lt;p&gt;Whichever route you take, compare the number of distinct parents in your table with the number of records you started with. On the Recipe 2 frame:&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;# orders: the list from Recipe 1
# lines: the frame from Recipe 2
&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;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 of 3 orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A number below your record count means the shape you chose for an empty array dropped a parent. Recipe 3 or Recipe 4 brings it back.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sourcing note: every recipe above ran on Python 3.11.16, pandas 3.1.1 and tabulate 0.10.0 on 2026-11-09, and each output block was copied from a real run; the ImportError string in Recipe 9 came from the same check with tabulate not yet installed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pandas</category>
      <category>json</category>
      <category>data</category>
    </item>
    <item>
      <title>Musk's Odyssey promise contradicts itself, and Grok noticed</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Mon, 09 Nov 2026 03:05:56 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/musks-odyssey-promise-contradicts-itself-and-grok-noticed-4519</link>
      <guid>https://dev.to/dan_lumatic/musks-odyssey-promise-contradicts-itself-and-grok-noticed-4519</guid>
      <description>&lt;p&gt;In July, Elon Musk promised that Grok Imagine would make a full-length Odyssey "historically accurate and true to the art of Homer". Episode one, &lt;a href="https://variety.com/2026/film/news/elon-musks-grok-ai-generated-odyssey-1236930662/" rel="noopener noreferrer"&gt;released on X&lt;/a&gt; on Oct 24-25, runs seven minutes. One of the first to flag the gap, that a poem is not a history, was Musk's own chatbot.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asked by WIRED, Grok called the episode an adaptation of a poem, not history.&lt;/li&gt;
&lt;li&gt;The July promise pulls in two directions, so no one could settle it.&lt;/li&gt;
&lt;li&gt;To check the full film, ask one yes/no question per spec line.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Grok called the episode an adaptation
&lt;/h2&gt;

&lt;p&gt;On Oct 26, &lt;a href="https://www.wired.com/story/grok-says-its-ai-generated-version-of-the-odyssey-is-not-historically-accurate/" rel="noopener noreferrer"&gt;WIRED&lt;/a&gt; asked Grok to assess the episode. Grok answered that it "is an adaptation of Homer's Odyssey, not a historically accurate reconstruction of real events". It added: "The Odyssey itself is not a historical document."&lt;/p&gt;

&lt;h2&gt;
  
  
  The promise contradicts itself
&lt;/h2&gt;

&lt;p&gt;The July sentence wants &lt;em&gt;historically accurate&lt;/em&gt; and &lt;em&gt;true to the art of Homer&lt;/em&gt; at once. Homer's art has a one-eyed Cyclops and meddling gods, and no history does. Developers call this conflicting acceptance criteria, like &lt;em&gt;must be real-time&lt;/em&gt; plus &lt;em&gt;must run full validation&lt;/em&gt; on an hour-long job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a testable version looks like
&lt;/h2&gt;

&lt;p&gt;A hypothetical rewrite, not the producers' words:&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="err"&gt;Source:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;named&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;translation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(say,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Fagles)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allowed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;deviations:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;compressed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;books,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;merged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;minor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;characters&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Fail:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scene&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;adds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;event,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dialogue,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;detail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;contain&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Cyclops scene trimmed to two minutes is compression and passes. The borderline case is a battle the poem mentions in one line, staged as a ten-minute set piece. It must invent specifics the source does not contain, so it fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check the full film
&lt;/h2&gt;

&lt;p&gt;Musk's end-of-year deadline still stood as of Nov 2. When the film lands, answer these from the film, its credits or its announcement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does it name its source translation?&lt;/li&gt;
&lt;li&gt;Does it state what it compressed or merged?&lt;/li&gt;
&lt;li&gt;Can you point to a scene that adds an event or detail the source lacks?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A pass is yes, yes, no. Anything else means the promise was untestable or broken.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dan writes the AI satire on super-intelligence.lol, where this piece first appeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>elonmusk</category>
      <category>satire</category>
      <category>discuss</category>
    </item>
    <item>
      <title>OpenAI Posted 722 Math Papers. Who Checks the Checker?</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Mon, 09 Nov 2026 03:04:11 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/openai-posted-722-math-papers-who-checks-the-checker-35d0</link>
      <guid>https://dev.to/dan_lumatic/openai-posted-722-math-papers-who-checks-the-checker-35d0</guid>
      <description>&lt;p&gt;On Oct 6, OpenAI put 722 mathematics manuscripts online, and only one had a human edit before release. Terence Tao calls this proof indigestion: more AI-written mathematics than the people who must judge it can absorb. A machine can now write a proof-shaped manuscript faster than anyone can check it. So the sharp question is who checks the checker.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks come in levels: a machine check, a named human review, released artifacts. OpenAI's three Oct 26 headline results got different mixes, from Sogge (machine only) to Yang-Mills (artifacts withheld pending review).&lt;/li&gt;
&lt;li&gt;The catalog shows no check status per paper, and The Atlantic says some appear not to prove their claims.&lt;/li&gt;
&lt;li&gt;Lean checks the proof steps, not whether the claim says what the mathematician meant. That second check is human.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  OpenAI's 41-page catalog of 722 papers marks no check status for any of them
&lt;/h2&gt;

&lt;p&gt;OpenAI published &lt;a href="https://openai.com/index/sharing-ai-progress-in-mathematics/" rel="noopener noreferrer"&gt;722 manuscripts covering 372 result families&lt;/a&gt; in a public GitHub repo, written by an unreleased internal model. The claims range from Khot's unique games conjecture to the quasi-Riemann hypothesis, a weaker version of the Riemann hypothesis. The Atlantic &lt;a href="https://www.theatlantic.com/technology/2026/10/openai-math-proofs-future/688940/" rel="noopener noreferrer"&gt;reported&lt;/a&gt; that some of the papers appear not to prove what they claim.&lt;/p&gt;

&lt;p&gt;Even a single headline can hide two check states. The Sept 30 version of quasi-Riemann, with zeros between 1/8 and 7/8, is Lean-verified. The later version, with a wider strip from 1/12 to 11/12, is not formally verified. A result's name does not tell you which version was checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to look for before you believe an AI-proved headline
&lt;/h2&gt;

&lt;p&gt;OpenAI's three Oct 26 headline results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;What it claims&lt;/th&gt;
&lt;th&gt;Who has checked it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sogge local smoothing&lt;/td&gt;
&lt;td&gt;A proof of Sogge's local smoothing conjecture, about how waves focus and fade, in every dimension of at least 2&lt;/td&gt;
&lt;td&gt;A machine only: Lean-formalized Oct 19. No named human re-check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETH disproof&lt;/td&gt;
&lt;td&gt;A disproof of the Exponential Time Hypothesis for 3-SAT&lt;/td&gt;
&lt;td&gt;A machine and named experts: Lean-verified Oct 17, shepherded by the hypothesis's original proposers Impagliazzo and Paturi plus Ryan Williams. OpenAI also committed to funding an open community conference on the claim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yang-Mills and QCD existence&lt;/td&gt;
&lt;td&gt;Existence proofs for Yang-Mills theory and QCD, two quantum physics theories&lt;/td&gt;
&lt;td&gt;No outside review yet: Lean formalization finished Oct 16, artifacts withheld pending review by a group led by Nobel laureate David Gross&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; open the primary announcement of any AI-proved result and look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Lean status for the result&lt;/li&gt;
&lt;li&gt;a named group that has finished reviewing it&lt;/li&gt;
&lt;li&gt;released files you can inspect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search the announcement text for the word Lean and for a reviewer's name, and check whether it links any files. Each missing item lowers how far you should trust the result. Only when all three are present, treat it as publicly checked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the list on the Yang-Mills row. A Lean status: yes, the formalization is finished. A named group that has finished reviewing: no, the Gross-led group's review is pending. Released files: no, they are withheld. One of three items is met, so treat it as unchecked in public.&lt;/p&gt;

&lt;p&gt;Only ETH has named people already working on the claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Lean check covers, and what it leaves to a human
&lt;/h2&gt;

&lt;p&gt;Lean is a proof assistant. It checks that every step follows from the claim as stated, and it cannot judge whether the stated claim says what the mathematician meant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lean is the checker, and the human who reads the formal statement and confirms it matches the intended conjecture is the checker's checker.&lt;/strong&gt; Picture a mathematician reading the Lean version of the ETH disproof and confirming it formalizes the real Exponential Time Hypothesis for 3-SAT, not a nearby, easier statement.&lt;/p&gt;

&lt;p&gt;If you write software, you know this shape. This analogy is ours, not OpenAI's or Tao's: a green test suite shows the code matches the tests, and only a person can say whether the tests describe what the customer wanted. Lean plays the test runner, and the formal statement plays the test.&lt;/p&gt;

&lt;p&gt;Tao's complaint behind proof indigestion is that the machines skip the explaining and judging that make a result usable. There is money for evaluation: the Simons Foundation has put $15M into it, $5M of it from OpenAI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who to wait for before you share the next one
&lt;/h2&gt;

&lt;p&gt;When the next AI-proved headline lands, run the three-item list and ask which version of the claim it describes. Then wait for the humans. For Yang-Mills that means the report from the Gross-led group. For ETH it means the open community conference OpenAI committed to funding.&lt;/p&gt;

&lt;p&gt;Dan writes the AI explainers on super-intelligence.rocks, where this piece first appeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>mathematics</category>
      <category>news</category>
    </item>
    <item>
      <title>Your AI agent's token bill is mostly input. Here is the field guide to cutting it.</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Mon, 09 Nov 2026 00:28:32 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/your-ai-agents-token-bill-is-mostly-input-here-is-the-field-guide-to-cutting-it-n40</link>
      <guid>https://dev.to/dan_lumatic/your-ai-agents-token-bill-is-mostly-input-here-is-the-field-guide-to-cutting-it-n40</guid>
      <description>&lt;h1&gt;
  
  
  Your AI agent's token bill is mostly input. Here is the field guide to cutting it.
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In a toy 10-turn agent session, 16,559 input tokens go out and 3,090 output tokens come back. At Claude Sonnet 4.5 prices input is still 52% of the cost, and all three cuts below shrink it to 3,290 input-equivalent tokens, 80% less.&lt;/li&gt;
&lt;li&gt;Truncating tool output and pruning old history alone cut the session's input by 62%, to 6,445 tokens.&lt;/li&gt;
&lt;li&gt;Caching a stable prefix bills it at 0.1x after the first call, but a prefix used only once costs 25% more than no caching.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your agent bill is bigger than the work you remember asking for, and the cause is mechanical. A model API is stateless, so every turn re-sends the system prompt, all tool output so far and the whole conversation. A file listing pasted on turn 2 is billed again on every turn after it. TechCrunch reported that Y Combinator CEO Garry Tan spends up to $200K a year on tokens, and at any scale the repeated input is the part you can shrink. Below: one script to measure it, and three cuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a 10-turn session's tokens go
&lt;/h2&gt;

&lt;p&gt;Turn 10 carries the results of turns 1 through 9, so a 10-turn session costs the sum of ten growing prompts. In the toy run below that sum is 16,559 input tokens. A real agent reply carries tool calls and code, so we model each reply at about 300 tokens, which makes 3,090 output tokens for the session.&lt;/p&gt;

&lt;p&gt;Output costs more per token. Anthropic's pricing page lists Claude Sonnet 4.5 at $3 per million input tokens and $15 per million output tokens, so one output token weighs 5 input tokens. Weighted by price, input is still 52% of the session, a bare majority. The script prints all three numbers.&lt;/p&gt;

&lt;p&gt;To count caching in the same units, we use input-equivalent tokens: a cache read counts as 0.1 of a token and a cache write as 1.25.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure first: count tokens for each part of a turn
&lt;/h2&gt;

&lt;p&gt;Start with your real baseline: log the &lt;code&gt;usage&lt;/code&gt; block of every API response (input and output tokens per call) and sum each per session. That total is your real number. The script then shows where it goes, by counting the system prompt, one tool output and the history separately. The biggest one tells you which cut to try first.&lt;/p&gt;

&lt;p&gt;The data is fake, so rerun the script on your own. Every figure is a tiktoken count except the cache lines and the cost-share lines, which are arithmetic from the vendors' documented multipliers and prices, with no API call.&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;# pip install tiktoken
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&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;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&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;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a coding agent. You read files, run commands, and edit code.
Always explain your plan before acting. Follow the repo style guide. When you
write tests, use the project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s existing fixtures. Never touch the migrations
folder without asking. Summarize diffs at the end of each task. Be concise but
complete. Handle errors gracefully and retry once on network failures.
[... style guide: 40 more lines of formatting rules ...]&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# One fake tool output: a 60-line file listing.
&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/module_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.py: def handler_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;(request) -&amp;gt; Response: ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# One fake model reply per turn. A real agent reply carries its tool calls and
# code, so we model it at a few hundred tokens.
&lt;/span&gt;&lt;span class="n"&gt;reply&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;I read the listing. handler_07 builds the response before it validates &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the request, which is why test_handler_07 fails. I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll move the check &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;above the response and add a regression test.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edit_file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/module_07.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- response = build(request, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n+ validate(request)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n+ response = build(request, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&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;history_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Turn &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: user asked something, agent read files, ran tests, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edited two files, tool output followed. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turns&lt;/span&gt;&lt;span class="p"&gt;)&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;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;cut&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="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[... &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cut&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; lines truncated ...]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tail&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;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;history_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="n"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;pruned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&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;truncated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;both&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&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;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_output&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;smaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;full&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system prompt:               &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;one raw tool output:         &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10 turns of history:         &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;history_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;one full turn as sent:       &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;history pruned (keep 3):     &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pruned&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;smaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pruned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% smaller)&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool output truncated:       &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;truncated&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;smaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truncated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% smaller)&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;both cuts:                   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;both&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;smaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;both&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% smaller)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A 10-turn session: turn i re-sends i earlier turns of history.
&lt;/span&gt;&lt;span class="n"&gt;naive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;session_cut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-turn input, no cuts:      &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-turn input, both cuts:    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;session_cut&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;session_cut&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% smaller)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Cut 3 arithmetic (documented multipliers, no API call): a cache write costs
# 1.25x, a cache read 0.1x. Stable prefix = system prompt + the 3 kept turns.
&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;history_of&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="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;

&lt;span class="c1"&gt;# All three cuts stacked, same model. Each turn re-sends the previous turn's
# system prompt + history as a cache read (0.1x), writes whatever the prefix
# grew by (1.25x), and bills the rest of the turn (the tool output) at 1x.
# Assumes pruning happens at fixed points, so the prefix stays valid after
# turn 3, and every call lands inside the 5-minute cache lifetime.
&lt;/span&gt;&lt;span class="n"&gt;stacked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;history_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;stacked&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-turn input, all 3 cuts:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stacked&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; input-equivalent tokens (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;stacked&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% smaller)&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-turn output (replies):    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Price-weighted: Sonnet 4.5 lists $3 per million input, $15 per million
# output, so one output token weighs 5 input tokens.
&lt;/span&gt;&lt;span class="n"&gt;OUT_WEIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input cost share, no cuts:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;naive&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;naive&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;OUT_WEIGHT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input cost share, 3 cuts:    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;stacked&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stacked&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;OUT_WEIGHT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stable prefix:               &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prefix x10, uncached:        &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; input-equivalent tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prefix x10, cached:          &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; input-equivalent tokens&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prefix used once, cached:    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; input-equivalent tokens&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;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system prompt:               93 tokens
one raw tool output:         960 tokens
10 turns of history:         1339 tokens
one full turn as sent:       2393 tokens
history pruned (keep 3):     1455 tokens (40% smaller)
tool output truncated:       1663 tokens (31% smaller)
both cuts:                   725 tokens (70% smaller)
10-turn input, no cuts:      16559 tokens
10-turn input, both cuts:    6445 tokens (62% smaller)
10-turn input, all 3 cuts:   3290 input-equivalent tokens (80% smaller)
10-turn output (replies):    3090 tokens
input cost share, no cuts:   52%
input cost share, 3 cuts:    18%
stable prefix:               494 tokens
prefix x10, uncached:        4940 input-equivalent tokens
prefix x10, cached:          1062 input-equivalent tokens
prefix used once, cached:    618 input-equivalent tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; replace &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;tool_output&lt;/code&gt;, &lt;code&gt;history_of&lt;/code&gt; and &lt;code&gt;reply&lt;/code&gt; with your real system prompt, one real tool result, your logged history and a typical reply.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cut 1: truncate tool output, keep the head and tail
&lt;/h2&gt;

&lt;p&gt;Tool output is often the biggest block, and most of it is never read again. &lt;code&gt;truncate&lt;/code&gt; keeps the first 600 characters and the last 200 and puts a marker in the middle. That takes one turn from 2,393 to 1,663 tokens, 31% smaller.&lt;/p&gt;

&lt;p&gt;Keep both ends because errors tend to show up at the top of a log and the summary line at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cap each tool result at a head, a tail and a marker that says how many lines were dropped, so the agent knows it can ask for more.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; log the length of every tool result for one real run. Whatever tops the list gets the cap first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cut 2: prune history once a result has been used
&lt;/h2&gt;

&lt;p&gt;After the agent has acted on a tool result, the raw result rarely needs to travel with it. The toy loop keeps only the last 3 turns, which cuts one turn from 2,393 to 1,455 tokens (40%), and both cuts together bring it to 725 (70%). A gentler version keeps a line per old result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pseudocode, language-neutral
for each message older than the last 3 turns:
    if it is a tool result:
        replace the body with one line, for example:
        "read src/auth.py (212 lines): login() calls verify_token(); test_login_expired fails"
    otherwise keep it as it is
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over-pruning makes the agent dumber. If an early turn found the name of the failing test, that fact has to survive the prune, as it does in the summary line above. The raw listings and bulky results can go, but any fact a later step will need to quote has to stay.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; after pruning, rerun one task you know the answer to. If it now fails or re-reads files it already read, you cut too much.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cut 3: keep the prefix identical so the cache can reuse it
&lt;/h2&gt;

&lt;p&gt;Anthropic's &lt;a href="https://docs.claude.com/en/docs/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;prompt caching docs&lt;/a&gt; put a cache read at 0.1x the base input price, and OpenAI's &lt;a href="https://platform.openai.com/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;prompt caching guide&lt;/a&gt; says cached input is discounted up to 95%. The script runs the Anthropic multipliers on the stable prefix, which is the system prompt plus the 3 kept turns, 494 tokens. Sent for 10 turns it bills like 4,940 tokens uncached and like 1,062 with a 1.25x write on turn 1 and 0.1x reads after. Sent once, it bills like 618, 25% more than not caching at all.&lt;/p&gt;

&lt;p&gt;That 494-token prefix sits below both vendors' 1,024-token minimums, so the toy cannot show this saving at its own scale. What carries over is the ratio on a real prefix: one write at 1.25x, then reads at 0.1x. The "all 3 cuts" line applies the same multipliers to the whole session, assuming pruning at fixed points and every call inside the 5-minute window.&lt;/p&gt;

&lt;p&gt;The 618 is how caching backfires. Three conditions decide which side you land on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The prefix must stay identical.&lt;/strong&gt; The cacheable order is tools, then system prompt, then history. Changing anything early breaks the match for everything after it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It must be reused in time.&lt;/strong&gt; On Anthropic a cache write costs 1.25x, and an entry lives 5 minutes by default. One call that never gets reused costs more than no caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It must be long enough.&lt;/strong&gt; OpenAI caches prompts of at least 1,024 tokens. Anthropic's minimum is 1,024 for Claude Sonnet 4.5 and ranges from 512 to 4,096 across its other models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The quiet killer is a timestamp in the system prompt. So is a request ID, or a tool list that comes back in a different order. Any of them makes the prefix different on every call, so on Anthropic you pay the 1.25x write each time and never see a 0.1x read. No error appears; the bill just goes up a little.&lt;/p&gt;

&lt;p&gt;Pruning rewrites the history, so the cache match ends where the prune starts: prune at fixed points instead of every turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order the prompt as tools, system prompt, then history, keep timestamps and IDs out of it, and send the same prefix again within 5 minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; on Anthropic, read &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; and &lt;code&gt;cache_read_input_tokens&lt;/code&gt; from each response's usage block. If reads stay at zero after the second call, something in your prefix changes between turns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What these cuts will not fix
&lt;/h2&gt;

&lt;p&gt;They shrink input. The output tokens you asked for stay, and after all three cuts the same arithmetic puts input at 18% of the session's cost, so output is now the bigger part of the bill. A loop that wanders for 20 turns instead of 10 re-sends the context ten more times, which no trim recovers.&lt;/p&gt;

&lt;p&gt;Where to start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool output is the biggest block: cut 1.&lt;/li&gt;
&lt;li&gt;History is the biggest block: cut 2.&lt;/li&gt;
&lt;li&gt;The prompt is large and stable: cut 3, and check the usage fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your 15-minute version for tomorrow: run the script with your real system prompt, one real tool result and your logged history. Whichever of the three counts is largest, apply that cut first and run the script again to see the drop. On the toy session the full stack takes 16,559 input tokens to 3,290 input-equivalent ones; your data will land elsewhere, and the rerun tells you where.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>An LLM edited a small model's weights directly and its math score rose 6x</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Thu, 05 Nov 2026 01:07:52 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/an-llm-edited-a-small-models-weights-directly-and-its-math-score-rose-6x-2d0l</link>
      <guid>https://dev.to/dan_lumatic/an-llm-edited-a-small-models-weights-directly-and-its-math-score-rose-6x-2d0l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPT-6 Astra, a frontier LLM, edited 0.016% of Qwen3.5-0.8B's weights directly, with no training run. MATH-500 went from 7.9% to 48.1%.&lt;/li&gt;
&lt;li&gt;The authors read most of that gain as unblocking: the base model kept running into its token limit instead of finishing. Astra also read, repaired and wrote small game-playing networks.&lt;/li&gt;
&lt;li&gt;Limits: no gain on Gemma 3 270M, one frontier LLM, models up to 0.8B, a blog post and not a peer-reviewed paper, and a prompt that hinted at the loop. The edited model and the logs are public.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;One edit to 0.016% of a small language model's weights lifted its math score from 7.9% to 48.1%. No training run, no gradients: GPT-6 Astra, a frontier LLM, read the weights directly and wrote the fix.&lt;/p&gt;

&lt;p&gt;Read the 6x with care. 93.0% of the base model's MATH-500 answers ran to the 32,768-token limit without finishing, and the authors read the gain as mostly unblocking: the model now finishes answers it could already work out. The assignment prompt also pointed Astra at that loop, and people have argued about it since Nov 2.&lt;/p&gt;

&lt;p&gt;MIT CSAIL researchers call this weight-space programming: a general-purpose LLM reads and writes a neural network's weights with no optimizer and no gradient descent, guided by a plain-language spec. Training has always been an indirect way to write weights, and their first results suggest a frontier model can write them directly. The post is "Sparks of Weight-Space Programming" by &lt;a href="https://rikiyat.github.io/blog/weight-space-programming/" rel="noopener noreferrer"&gt;Rikiya Takehi, Jayden Teoh, Alex Zhang and Omar Khattab&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the Qwen claim yourself; replaying the whole run takes a day
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The edited model is on Hugging Face as &lt;code&gt;RikiyaT/Qwen3.5-0.8B-weight-space-programming&lt;/code&gt;, and only two tensors differ from base.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hugging Face's Ahsen Khaliq rebuilt it from the logs within a day and shipped a live demo. HF's independent run of the full MATH-500 set scored 51.0%.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; run the same math questions through base Qwen3.5-0.8B and the edited model with the same generation cap, and count how many answers hit the cap. The table below shows the authors' rates at a 32,768-token limit. Then clone the &lt;a href="https://github.com/RikiyaT/lm_understand-logs" rel="noopener noreferrer"&gt;logs repo&lt;/a&gt; and open &lt;code&gt;exp8_math/runs/p50c/turns/t001/prompt.txt&lt;/code&gt;. It is the exact assignment Astra received. Read it before any commentary, including this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Replaying Astra's own session is the expensive part, though the logs hold everything sent to and from Astra, all code and all evaluations. Coverage of the logs (dailyaibrief.com, clauding.de) reports that the successful 50-pass run took 158 conversational turns and 80 Python runs over 20.3 hours. No fine-tuning cost is given to compare that with.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Astra took a loop out of 131,072 weights
&lt;/h2&gt;

&lt;p&gt;Qwen's own model card warns Qwen3.5-0.8B is prone to "loops compared to other Qwen3.5 models, which may prevent it from terminating generation properly." Of the base model's first 96 practice answers, 85 ran to the 32,768-token limit.&lt;/p&gt;

&lt;p&gt;Astra worked in a Python notebook on practice prompts from the GSM8K and MATH training sets. A forward pass is one run of the model, and the budget capped how many runs Astra got: 10, 25 or 50. It traced the looping to one direction, a single pattern written by the MLP blocks of layers 15 and 19. It then subtracted that direction from 64 output columns in each block, so those layers no longer write it. That is 131,072 weights, 0.016% of the model, and the rest stays as trained.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Base Qwen3.5-0.8B&lt;/th&gt;
&lt;th&gt;Edited (50 passes)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MATH-500&lt;/td&gt;
&lt;td&gt;7.9%&lt;/td&gt;
&lt;td&gt;48.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GSM8K&lt;/td&gt;
&lt;td&gt;13.8%&lt;/td&gt;
&lt;td&gt;67.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IFEval&lt;/td&gt;
&lt;td&gt;21.0%&lt;/td&gt;
&lt;td&gt;62.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MATH-500 answers hitting the 32,768-token limit&lt;/td&gt;
&lt;td&gt;93.0%&lt;/td&gt;
&lt;td&gt;33.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;It needed the full 50-pass budget: at 10 and 25 passes, MATH-500 reached only 12.3% and 14.1%.&lt;/strong&gt; Even at 50 passes, 33.1% of answers still hit the limit. IFEval, which tests instruction following, rose too, which fits the unblocking reading.&lt;/p&gt;

&lt;p&gt;A skeptic can call this a bug fix for a documented loop. None of the coverage I read tests whether a decoding-side fix, such as a repetition penalty, would match it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did Astra find the loop, or was it told where to look?
&lt;/h2&gt;

&lt;p&gt;Researcher Lucas Beyer read the assignment prompt, as Turing Post reported on Nov 2. It told Astra that if Qwen "repeats itself and never finishes, finding and fixing what drives that repetition counts." Coauthor Jayden Teoh replied that the team gave the short assignment via Claude Code.&lt;/p&gt;

&lt;p&gt;There is a fair counterpoint. Qwen's model card had already flagged the loop, so pointing at it was reasonable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third-party re-run confirms the edit works. How much credit Astra deserves for finding the loop, given the hint, stays open.&lt;/strong&gt; A cleaner test would rerun the assignment with that sentence removed. None of the coverage I read reports one, and the prompt is public.&lt;/p&gt;

&lt;h2&gt;
  
  
  Astra also read, repaired and wrote small networks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read.&lt;/strong&gt; With no list of games and no tools, Astra named the game each of four trained PPO networks played, from raw weights alone. For Breakout it noticed that first-layer weights concentrate along the bottom row, where a paddle lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix.&lt;/strong&gt; In a Kinetix car-ramp task, a PPO policy drove straight at the ramp and stayed stuck. Astra got the weights, a scene description and one recorded episode, with no tools. In one reply it changed 3.5% of the weights and took success from 0% to 100% over 1,024 episodes. The edit added a small path that backs the car up past the red strip, then hands control back to the trained policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write.&lt;/strong&gt; Given only a game's rules and source code, with no data, loss, gradients, tools or rollouts, Astra wrote MinAtar networks that beat PPO trained for 100M steps in all three games. In Breakout the score was 517.4 versus 15.8. Against PPO at 500M steps, Astra still wins Breakout by 20x.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it breaks: Gemma 3 270M gained nothing established
&lt;/h2&gt;

&lt;p&gt;Gemma 3 270M rarely loops: 8.5% of its answers hit the token limit. MATH-500 started at 6.8% and four edits scored 7.0%, 7.6%, 7.7% and 7.0%. None was established, and Astra said so itself. Its errors are ordinary arithmetic and algebra mistakes, with no single component to switch off. The authors put it this way: "Editing weights could remove an obstacle that hid an ability, but it could not add an ability the model lacked."&lt;/p&gt;

&lt;p&gt;In a separate test on two fine-tuned Gemma copies, Astra failed from weights alone in all 4 attempts, so some hidden behavior shows only in activations.&lt;/p&gt;

&lt;p&gt;One frontier LLM was tested, on models up to 0.8B parameters. This is a blog post with logs, not a peer-reviewed paper, and the authors themselves call it "sparks" and "very early." They are building a benchmark and harness to make this "a rigorous, reproducible engineering discipline."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: numbers come from the authors' post and logs, plus the Hugging Face model card for the 51.0% re-run.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to watch, and what to try on your own small model
&lt;/h2&gt;

&lt;p&gt;My read: the practical use today is diagnosing and surgically repairing small models. If one of yours underperforms, check how often its answers hit the generation cap before you blame its capability. In Qwen's case, 85 of the first 96 practice answers hit it. Then ask whether a decoding-side fix would do the same job; none of the coverage I read reports that comparison.&lt;/p&gt;

&lt;p&gt;Watch for a replication above 0.8B parameters, and for a second model whose fault is one removable direction, which Gemma's was not.&lt;/p&gt;

&lt;p&gt;Training is an indirect way to write weights, and a frontier LLM has now written some directly and beaten trained networks on small tests.&lt;/p&gt;

&lt;p&gt;Dan writes the AI explainers on super-intelligence.rocks, where this piece first appeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why banks block AI agents at the login</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Tue, 03 Nov 2026 00:34:09 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/why-banks-block-ai-agents-at-the-login-20cj</link>
      <guid>https://dev.to/dan_lumatic/why-banks-block-ai-agents-at-the-login-20cj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bank login carries only a password, so an agent using your password and a thief using a phished one look identical, and the bank refuses both. Charles Schwab says a given agent might hit its login "30, 40, 50 times" a week and gets blocked.&lt;/li&gt;
&lt;li&gt;Bank of America's customer email and Wells Fargo's Nov 24 account terms can leave the customer responsible for what an agent does with their password.&lt;/li&gt;
&lt;li&gt;Read-only access the bank approves is open today. Moving money needs credentials issued to the agent, and the US rule meant to replace password-based access is frozen in court.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bank of America emailed customers a message titled "Protecting your finances in the AI world," telling them not to share passwords with AI agents. If they do, the email says, they are "responsible for activity resulting from that access."&lt;/p&gt;

&lt;p&gt;If you build agents that touch customer accounts, this is likely why your login-based demo gets blocked. From the bank's side of the glass, a logged-in agent and a stolen session are the same thing, so banks refuse the login and leave the customer responsible for whatever slips through.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bank login cannot tell your agent from a phished password
&lt;/h2&gt;

&lt;p&gt;A login checks one thing: whether the password matches the one the bank has on file. The customer, the customer's agent and a criminal with a phished password all present the same proof.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.axios.com/2026/10/22/ai-agents-banks-unauthorized-accounts-access" rel="noopener noreferrer"&gt;Axios reported on Oct 22&lt;/a&gt;, citing people familiar with the matter, that banks "were detecting outside AI agents inside customer accounts", including some "that attempted actions that appeared independent of their users' instructions". &lt;a href="https://www.artificialintelligence-news.com/news/banks-block-ai-agents-open-banking-rule/" rel="noopener noreferrer"&gt;AI News&lt;/a&gt; adds a counterpoint: Morgan Stanley, which has blocked third-party agents, "had not seen any unauthorised or rogue instances," a person familiar with the bank told Reuters. The agents in question are consumer tools such as Meta's Muse, which can connect to accounts with a customer's authorization to monitor balances and transactions.&lt;/p&gt;

&lt;p&gt;To the bank, an agent's login is a correct password from a device it has never seen, which is also how a criminal's login looks. The one defense available at that door is to refuse it. Schwab's CEO says the firm is "keeping rogue agents from being able to log in on the client's behalf."&lt;/p&gt;

&lt;p&gt;The blocks show a bank can spot agent-like traffic. A blocked attempt still says nothing about whose agent it was, because the password is all the form carries. That is our reading of the evidence; no bank states it in these words.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read-only access the bank approves is open today
&lt;/h2&gt;

&lt;p&gt;Axios reported that most banks have approved, or are moving toward, read-only access that lets an agent view account information without moving money. Schwab plans to let outside AI models check balances and research through its own MCP servers, but not move money: "We control the authentication experience." These agents arrive through a path the bank built, so the bank can recognize them and the identity question never comes up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; Check how your integration connects to the bank. If it asks users for a bank username and password, expect trouble: Schwab blocks agent logins, and TD tells clients never to share their log-in credentials with an AI agent. If it connects through the bank's own approval and only reads balances and transactions, it uses the access banks already approve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Bank of America and Wells Fargo put the liability on the customer
&lt;/h2&gt;

&lt;p&gt;TechCrunch, reporting on the Bank of America email, puts the general rule this way: &lt;strong&gt;"Account agreements at most banks hold customers responsible for anyone they give their log-in to."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wells Fargo is writing it down for agents. As AI News summarizes it, the updated Online Access Agreement, effective Nov 24, says that, subject to applicable law, instructions and transactions from an agent given a customer's login details or authority "can be treated as the customer's own without further verification." Customers are also made responsible for unintended actions and for actions that exceed the authority they gave.&lt;/p&gt;

&lt;p&gt;For a builder, that means the customers using a login-based integration are told not to share credentials, the bank may block the login, and the customer can carry the risk if something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each bank stands
&lt;/h2&gt;

&lt;p&gt;Per reporting through Nov 1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bank&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;On the record&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JPMorgan Chase&lt;/td&gt;
&lt;td&gt;Blocks external agents from acting for retail customers; some third-party tools view data only&lt;/td&gt;
&lt;td&gt;Spokeswoman to CNBC; Bloomberg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bank of America&lt;/td&gt;
&lt;td&gt;Imposed limits on agents&lt;/td&gt;
&lt;td&gt;Customer email warning (TechCrunch, Oct 27)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wells Fargo&lt;/td&gt;
&lt;td&gt;Restricts third-party agent access&lt;/td&gt;
&lt;td&gt;Nov 24 agreement (AI News)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charles Schwab&lt;/td&gt;
&lt;td&gt;Blocks agent logins; outside AI models connect on its terms, no money movement&lt;/td&gt;
&lt;td&gt;CEO Rick Wurster: agents blocked from logging in on clients' behalf (InvestmentNews, Oct 28)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BMO, CIBC, RBC, TD, Scotiabank&lt;/td&gt;
&lt;td&gt;Four block personal AI agents; Scotiabank limits third-party agents while it monitors&lt;/td&gt;
&lt;td&gt;TD's advice: keep log-in credentials away from AI agents (CBC, Oct 29)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Moving money needs agent-scoped credentials; the rule meant to replace password sharing is frozen
&lt;/h2&gt;

&lt;p&gt;An agent a bank could admit would carry a credential of its own, scoped to specific permissions, revocable without touching the customer's login, with liability set in advance. OAuth-style tokens show the shape: an app acts for you within a limited scope you can cancel without changing your password.&lt;/p&gt;

&lt;p&gt;As AI News reports, relaying American Banker, the Personal Agent Protocol is an open standard led by Sierra and Meta that "sets out how personal AI agents identify themselves to businesses, establish whom they represent and limit their access to what the customer has authorised". It is built on OAuth, and the customer decides whether an agent gets read-only or write access. Bank of America, BBVA, Synchrony and Wells Fargo have joined it, even as Bank of America and Wells Fargo restrict the credential-based agents already in use.&lt;/p&gt;

&lt;p&gt;The US rule in the picture is the CFPB's Section 1033 open banking rule, finalised in October 2024 and meant to replace credential-based access. As AI News reported, the rule "requires banks to maintain a developer interface, typically an API, through which authorised third parties can access customer data", and "bars banks from relying on a third party's use of customer credentials" to reach that interface. A federal court has enjoined the CFPB from enforcing it, and as of August 2026 a revised proposal had gone to White House review without being published. As AI News put it: "With the rule on hold, there is no regulated route for AI agents into bank accounts."&lt;/p&gt;

&lt;p&gt;Two things to watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether Bank of America or Wells Fargo accept write-capable agents under the protocol, since membership signals a commitment to agent identity and a customer-chosen write option.&lt;/li&gt;
&lt;li&gt;Whether the revised Section 1033 rule gets published, since it would give agents a regulated way in without a password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until a bank can issue an agent credentials of its own, a logged-in agent and a stolen session look the same from the bank's side, so expect the login to stay closed. If you build anything that touches customer accounts, build for the read-only lane the bank approves.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: Axios (Oct 22, via CBC News and AI News), CBC News (Oct 29), TechCrunch (Oct 27), InvestmentNews (Oct 28), CNBC (Nov 1), ZDNET (Oct 29), AI News (Oct 30, relaying American Banker, Reuters and Bloomberg).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dan writes the AI explainers on super-intelligence.rocks, where this piece first appeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Image to base64: when inlining helps and when it hurts</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Tue, 03 Nov 2026 00:29:03 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/image-to-base64-when-inlining-helps-and-when-it-hurts-b8g</link>
      <guid>https://dev.to/dan_lumatic/image-to-base64-when-inlining-helps-and-when-it-hurts-b8g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base64 turns every 3 bytes into 4 characters, about 33% bigger. On a gzipped response an image's penalty shrinks to about 1% (1.010x of the original in our test), but an uncompressed response pays the full third.&lt;/li&gt;
&lt;li&gt;Rule of thumb: inline only an icon of a few KB that your own code owns and uses once. Serve everything else as a file, because an inlined image re-downloads with every revision of the file that holds it.&lt;/li&gt;
&lt;li&gt;In the browser use &lt;code&gt;FileReader.readAsDataURL&lt;/code&gt;. In Python use &lt;code&gt;base64.b64encode(...).decode("ascii")&lt;/code&gt;. &lt;code&gt;btoa&lt;/code&gt; throws on any character outside Latin-1.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;A reviewer writes "just base64 it", and the image moves into the file that holds it. It can no longer be cached on its own, it comes back with every deploy of that page, and it is a third bigger before compression. Nobody on the thread can say what the change cost, because the diff only shows a long string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base64 adds a third; gzip hides it for images
&lt;/h2&gt;

&lt;p&gt;Every 3 bytes of input become 4 characters of output, so the encoded size is 4/3 of the original plus &lt;code&gt;=&lt;/code&gt; padding to fill the last group (&lt;a href="https://www.rfc-editor.org/info/rfc4648" rel="noopener noreferrer"&gt;RFC 4648&lt;/a&gt;, section 4). We encoded random bytes in Python 3.14 to see the ratio. Padding adds at most two characters, which is why the smallest input is slightly worse and larger ones settle at 4/3:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Original bytes&lt;/th&gt;
&lt;th&gt;Base64 bytes&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1,336&lt;/td&gt;
&lt;td&gt;1.3360x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;13,336&lt;/td&gt;
&lt;td&gt;1.3336x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;133,336&lt;/td&gt;
&lt;td&gt;1.3334x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The usual objection is that gzip fixes this. For images it nearly does, on a compressed response. We used 50,000 random bytes as a stand-in for a PNG or JPEG, which are already compressed. As base64 they became 66,668 bytes. Gzip at level 9 shrank that text to 50,508 bytes, which is 1.010x the original binary. Gzip on the raw bytes gave 50,038 (1.001x).&lt;/p&gt;

&lt;p&gt;That ~1% figure holds for already-compressed images. An uncompressed API response, an email body or a stylesheet your server does not compress pays the full third. Even in the best case, gzipped base64 stayed above the raw size in our test, so inlining at most ties on bytes.&lt;/p&gt;

&lt;p&gt;SVG is the exception worth knowing. It is text, and a data URL does not require base64: the syntax is &lt;code&gt;data:[&amp;lt;media-type&amp;gt;][;base64],&amp;lt;data&amp;gt;&lt;/code&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;), so &lt;code&gt;;base64&lt;/code&gt; is optional. In our reading, URL-encoding an SVG usually beats base64 because only the special characters grow. In our test a 13,800-byte SVG became 18,400 bytes as base64 (1.333x).&lt;/p&gt;

&lt;h2&gt;
  
  
  Inline an image only if it is tiny, code-owned and used once
&lt;/h2&gt;

&lt;p&gt;Run each candidate through three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is it small? &lt;strong&gt;Our rule of thumb is a few KB at most.&lt;/strong&gt; That is a heuristic, not a measurement. A 16-pixel icon usually passes, and a photo almost never does.&lt;/li&gt;
&lt;li&gt;Does it change only when the file that embeds it changes? A logo mark in your CSS qualifies. A user avatar does not.&lt;/li&gt;
&lt;li&gt;Does it appear once? Each inline copy adds its full encoded size to the uncompressed file, to memory and to parse work. A compressed wire softens this, because gzip back-references the repeated copy. We embedded a 20,000-byte random payload as base64 once and it gzipped to 20,250 bytes. Embedded twice, it gzipped to 20,567 (1.016x), not 2.0x. You still ship the full size to anyone without compression.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any answer is no, serve a file. A file gets its own URL, its own cache entry and its own headers, and lazy loading can skip it entirely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; search your stylesheets for &lt;code&gt;url(data:&lt;/code&gt; and run each match through the three questions. Every hit that fails one should become a file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  An inlined image re-downloads with every deploy
&lt;/h2&gt;

&lt;p&gt;An inlined image has no cache entry of its own. It lives inside its host: your HTML page, your CSS bundle or your JS bundle. &lt;strong&gt;Change one line of that file, deploy, and every returning visitor downloads the image again with it.&lt;/strong&gt; A served file keeps its own entry and its own validators through all of those deploys. A data URL has neither.&lt;/p&gt;

&lt;p&gt;A quick piece of arithmetic, not a measurement: a 20,000-byte image inlined becomes 26,668 bytes. If its host file ships 10 times and a visitor returns after each deploy, that visitor downloads 266,680 bytes for an image that never changed. As a file, they download 20,000 bytes once. Compression shrinks the inlined number; the already-compressed file itself barely compresses at all.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;data:&lt;/code&gt; URL is also an opaque origin. MDN states it directly: "Data URLs are treated as unique opaque origins by modern browsers" and "top-level navigation to data: URLs is blocked in all modern browsers". You cannot link to one as a page or open one in a new tab to debug it. If you run a Content Security Policy, &lt;code&gt;img-src&lt;/code&gt; must also allow &lt;code&gt;data:&lt;/code&gt;, and in our reading that allows every data image, not only yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript: FileReader for files, TextEncoder before btoa
&lt;/h2&gt;

&lt;p&gt;For a user-selected file, let the browser do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `file` comes from &amp;lt;input type="file"&amp;gt; or a drop event&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// data:image/png;base64,iVBORw0KGgoAAAANSU&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ran this in Chromium with a 1x1 PNG. &lt;code&gt;reader.result&lt;/code&gt; is the complete data URL, ready for &lt;code&gt;img.src&lt;/code&gt;. If you need only the base64 part, take everything after the first comma.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;btoa&lt;/code&gt; is the other tool people reach for, and it has a trap. It accepts only Latin-1 characters and throws on anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;café ☕&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// InvalidCharacterError&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;café ☕&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="c1"&gt;// Y2Fmw6kg4piV&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to encode the text to UTF-8 bytes first, then turn each byte into one character for &lt;code&gt;btoa&lt;/code&gt;. That is for strings. The spread in &lt;code&gt;String.fromCharCode(...bytes)&lt;/code&gt; passes every byte as a function argument, so it suits short strings only. On Node v24.22.1 it worked with 120,000 arguments and threw &lt;code&gt;RangeError&lt;/code&gt; at 200,000. For image files, skip &lt;code&gt;btoa&lt;/code&gt; and use &lt;code&gt;readAsDataURL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python: b64encode returns bytes, so decode it
&lt;/h2&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;base64&lt;/span&gt;

&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x4E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFE&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;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&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;ascii&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="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&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;ascii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# iVBOR/v//g==
# iVBOR_v__g==
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;b64encode&lt;/code&gt; returns &lt;code&gt;bytes&lt;/code&gt;, so &lt;code&gt;.decode("ascii")&lt;/code&gt; gives you a string you can drop into an f-string such as &lt;code&gt;f"data:image/png;base64,{encoded}"&lt;/code&gt;. The standard alphabet uses &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt;. &lt;code&gt;urlsafe_b64encode&lt;/code&gt; swaps them for &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt; (RFC 4648, section 5), which is what you want in a URL or a filename.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the host file's size after every merge that touches images
&lt;/h2&gt;

&lt;p&gt;Inlining usually happens by accident: a bundler option, a loader default, a helpful teammate. The symptom is a host file that grows by about 4/3 of an image's size. Catch it in three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Record the size of your CSS and JS bundles on your main branch, for example &lt;code&gt;wc -c dist/app.css&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;After a merge, compare. A jump close to 4/3 of some image's byte size means that image got inlined.&lt;/li&gt;
&lt;li&gt;Run it through the three questions above. If it fails one, extract it and serve it as a file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Put the size check in CI with a threshold, and the review comment stops being a guess about cost.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sourcing note: ratios and sizes come from random bytes encoded on Python 3.14 on 2026-11-02. The gzip repeat test and the spread limit ran on 2026-11-03 on Node v24.22.1, and the other snippets ran on Node 24, Chromium and Python 3.14. The HTTP/2 and HTTP/3 point, the cache and CSP points and the SVG advice are our reading of how browsers behave, and the deploy example is arithmetic. The opaque-origin and navigation quotes come from MDN.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>python</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Gemini 4 Carbon tops the charts. You still can't use it.</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Sun, 01 Nov 2026 05:11:07 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/gemini-4-carbon-tops-the-charts-you-still-cant-use-it-7jo</link>
      <guid>https://dev.to/dan_lumatic/gemini-4-carbon-tops-the-charts-you-still-cant-use-it-7jo</guid>
      <description>&lt;p&gt;Google calls Gemini 4 Carbon "our strongest model", and it tops the Artificial Analysis Intelligence Index at 59. As of Nov 1, almost nobody can use it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Carbon is #1 on an independent ranking and posts a headline cybersecurity score.&lt;/li&gt;
&lt;li&gt;Carbon is a &lt;strong&gt;showroom model&lt;/strong&gt;: the price is listed in public, but access is invite-only, first defenders, then partners, and everyone else gets it "as soon as possible" with no date attached.&lt;/li&gt;
&lt;li&gt;While you wait, read the benchmark tables and price your prompts at both rates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ranked first, pitched at cybersecurity
&lt;/h2&gt;

&lt;p&gt;Google announced the model on Oct 28 with a cybersecurity focus. Its headline number is "88.2% on SEC-bench Pro, autonomously uncovering complex flaws buried in real-world software".&lt;/p&gt;

&lt;h2&gt;
  
  
  Defenders, partners, then a promise
&lt;/h2&gt;

&lt;p&gt;Access starts with "a subset of cyber defenders", then "trusted partners". Google describes "a deliberate approach to rollout", which is a reasonable thing to say about a model that finds software flaws on its own. &lt;a href="https://arstechnica.com/ai/2026/10/google-announces-gemini-4-carbon-its-latest-frontier-ai-model-that-you-cant-use/" rel="noopener noreferrer"&gt;Ars Technica's headline&lt;/a&gt; puts the same fact more bluntly: "...its latest frontier AI model that you can't use".&lt;/p&gt;

&lt;p&gt;The price, on the other hand, is already on the &lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/introducing-gemini-4-carbon/" rel="noopener noreferrer"&gt;Google announcement&lt;/a&gt;. The tag reads "$2 per million input tokens and $10 per million output tokens". Ars reports those are introductory rates that will double later.&lt;/p&gt;

&lt;p&gt;That makes Carbon a showroom model: spotlight, velvet rope, price tag taped to the pedestal. Any launch that tops a ranking, publishes its rates and names its approved users fits the same description.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read, price, and check one page
&lt;/h2&gt;

&lt;p&gt;Read the benchmark tables so you know what you are waiting for, and price your prompts now so the bill holds no surprises on release day. Then keep an eye on Google's announcement page. Access has opened when it stops naming "a subset of cyber defenders" and "trusted partners" as the audience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; take a prompt with 1M input tokens and 200K output tokens. It costs $4 at the intro rate, $8 if both rates double as Ars reports.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The price is public. The release date is not.&lt;/p&gt;

&lt;p&gt;Sources: Google blog (Oct 28, 2026), Ars Technica (Oct 28, 2026), Reuters (Oct 28, 2026). Verified Nov 1, 2026: no release date announced.&lt;/p&gt;

&lt;p&gt;Dan writes the AI satire on super-intelligence.lol, where this piece first appeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>google</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>JSON Patch explained: how to apply one in JavaScript and Python</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Thu, 29 Oct 2026 07:39:09 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/json-patch-explained-how-to-apply-one-in-javascript-and-python-3lbd</link>
      <guid>https://dev.to/dan_lumatic/json-patch-explained-how-to-apply-one-in-javascript-and-python-3lbd</guid>
      <description>&lt;p&gt;A PUT sends the whole document, and if someone changed a field while you were looking, your copy can overwrite it. A JSON Patch (&lt;a href="https://datatracker.ietf.org/doc/html/rfc6902" rel="noopener noreferrer"&gt;RFC 6902&lt;/a&gt;) sends only the difference, as data a reviewer can read line by line. Below is one patch that uses all six operations, applied in JavaScript and Python. After that come how to generate and send one, and what each library leaves behind when a patch fails halfway.&lt;/p&gt;

&lt;h2&gt;
  
  
  One patch can use all six operations
&lt;/h2&gt;

&lt;p&gt;A patch is a JSON array of operation objects. Each has an &lt;code&gt;op&lt;/code&gt;, a &lt;code&gt;path&lt;/code&gt;, and sometimes a &lt;code&gt;value&lt;/code&gt; or a &lt;code&gt;from&lt;/code&gt;. The six ops are &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;move&lt;/code&gt;, &lt;code&gt;copy&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We will reuse this document and patch throughout:&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"red sneaker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&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="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"in_stock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&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="s2"&gt;"summer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sale"&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;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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/in_stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;59.0&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"red sneaker, wide fit"&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/display_name"&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"move"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/tags/1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/tags/0"&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remove"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/sizes/2"&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;p&gt;In order, it checks the stock flag, adds a price, renames the product, copies the new name into &lt;code&gt;display_name&lt;/code&gt;, moves &lt;code&gt;sale&lt;/code&gt; to the front of the tags and drops the last size.&lt;/p&gt;

&lt;p&gt;Ops run in order, and every path refers to the document as the previous op left it. Two &lt;code&gt;remove&lt;/code&gt; ops on &lt;code&gt;/sizes/0&lt;/code&gt; against &lt;code&gt;[40, 41, 42]&lt;/code&gt; delete 40 and then 41, because the array shifted after the first one. &lt;strong&gt;When you build a patch by hand, count each index against the document as it will look at that step.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply it in JavaScript: the original document changes in place
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;fast-json-patch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apply-patch.mjs&lt;/span&gt;
&lt;span class="c1"&gt;// fast-json-patch ships CommonJS, so take the default import and destructure it&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-json-patch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;applyPatch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red sneaker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/in_stock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;59.0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red sneaker, wide fit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/display_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tags/1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tags/0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/sizes/2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyPatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// doc is already the patched document; newDocument is optional in this mode&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;patched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;newDocument&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patched&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on Node 24:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"name":"red sneaker, wide fit","sizes":[40,41],"in_stock":true,"tags":["sale","summer"],"price":59,"display_name":"red sneaker, wide fit"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;applyPatch&lt;/code&gt; returns one result per op, each carrying the &lt;code&gt;newDocument&lt;/code&gt; as it stood after that op. The last result is the final document, and it is the same object as &lt;code&gt;doc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply it in Python: you get a new document back
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;jsonpatch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apply_patch.py
&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;import&lt;/span&gt; &lt;span class="n"&gt;jsonpatch&lt;/span&gt;

&lt;span class="n"&gt;doc&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;name&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;red sneaker&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;sizes&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="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_stock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&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;summer&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;sale&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="n"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonpatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JsonPatch&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;op&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;test&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;path&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;/in_stock&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&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;op&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;add&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;path&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;/price&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;59.0&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;op&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;replace&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;path&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;/name&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;value&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;red sneaker, wide fit&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;op&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;copy&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;from&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;/name&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;path&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;/display_name&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;op&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;move&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;from&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;/tags/1&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;path&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;/tags/0&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;op&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;remove&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;path&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;/sizes/2&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&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;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on Python 3.14:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"name": "red sneaker, wide fit", "sizes": [40, 41], "in_stock": true, "tags": ["sale", "summer"], "price": 59.0, "display_name": "red sneaker, wide fit"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output matches, apart from &lt;code&gt;59&lt;/code&gt; in JavaScript against &lt;code&gt;59.0&lt;/code&gt; in Python, which is how each language prints the same number. &lt;code&gt;JsonPatch&lt;/code&gt; takes a parsed list, so a patch that arrives as JSON text goes through &lt;code&gt;json.loads&lt;/code&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate the patch from two documents
&lt;/h2&gt;

&lt;p&gt;If you already hold a &lt;code&gt;before&lt;/code&gt; and an &lt;code&gt;after&lt;/code&gt;, let the library write the patch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-json-patch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;compare&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red sneaker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red sneaker, wide fit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="c1"&gt;// [{"op":"replace","path":"/in_stock","value":false},{"op":"add","path":"/sizes/3","value":43},{"op":"replace","path":"/name","value":"red sneaker, wide fit"}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonpatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make_patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# the same two dicts
&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;patch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# [{"op": "replace", "path": "/name", "value": "red sneaker, wide fit"}, {"op": "add", "path": "/sizes/3", "value": 43}, {"op": "replace", "path": "/in_stock", "value": false}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The libraries emit the same three ops in a different order, and applying either patch to &lt;code&gt;before&lt;/code&gt; gives back &lt;code&gt;after&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send it as an HTTP PATCH
&lt;/h2&gt;

&lt;p&gt;The body is the patch array, and the media type is &lt;code&gt;application/json-patch+json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.example.com/things/42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json-patch+json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local echo server we sent this to reported method &lt;code&gt;PATCH&lt;/code&gt;, that media type and the patch as the request body. In Python the same call is &lt;code&gt;requests.patch(url, json=patch, headers={"Content-Type": "application/json-patch+json"})&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A failed patch leaves different things behind in each library
&lt;/h2&gt;

&lt;p&gt;Picture an order object that is half updated in memory after a patch fails, and then gets saved anyway. Take a patch whose second op fails:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&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;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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;p&gt;Applied to &lt;code&gt;{"a": 1}&lt;/code&gt;, the &lt;code&gt;test&lt;/code&gt; fails because &lt;code&gt;a&lt;/code&gt; is 1. Here is what each library did:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript, default:&lt;/strong&gt; &lt;code&gt;applyPatch&lt;/code&gt; throws &lt;code&gt;PatchError&lt;/code&gt;, and the document afterwards is &lt;code&gt;{"a":1,"b":2}&lt;/code&gt;. Op 1 already ran on your object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript, opt-out:&lt;/strong&gt; &lt;code&gt;applyPatch(doc, patch, false, false)&lt;/code&gt; still throws, and the original stays &lt;code&gt;{"a":1}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python, default:&lt;/strong&gt; &lt;code&gt;patch.apply(doc)&lt;/code&gt; raises &lt;code&gt;JsonPatchTestFailed&lt;/code&gt;, and the original stays &lt;code&gt;{"a": 1}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python, &lt;code&gt;in_place=True&lt;/code&gt;:&lt;/strong&gt; it raises the same error, and the document is left as &lt;code&gt;{"a": 1, "b": 2}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; apply the three-op patch above to &lt;code&gt;{"a": 1}&lt;/code&gt; with the call you actually use, and print the document in the &lt;code&gt;catch&lt;/code&gt; or &lt;code&gt;except&lt;/code&gt; branch. If &lt;code&gt;b&lt;/code&gt; is there, a failed patch half-applies in your setup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put &lt;code&gt;test&lt;/code&gt; ops at the top of the patch and a stale document fails before the first change runs; they cannot catch a later op failing, so the stronger fix is to apply the patch to a copy (the default in Python, the &lt;code&gt;false, false&lt;/code&gt; form in JavaScript) and keep the original until the whole patch has worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript mutates by default and Python does not, so the same failed patch can corrupt your object in one language and leave it clean in the other.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Paths are JSON Pointers, and two characters need escaping
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;from&lt;/code&gt; is a JSON Pointer (&lt;a href="https://datatracker.ietf.org/doc/html/rfc6901" rel="noopener noreferrer"&gt;RFC 6901&lt;/a&gt;). Segments are separated by &lt;code&gt;/&lt;/code&gt;, and array items are addressed by index, so &lt;code&gt;/sizes/2&lt;/code&gt; is the third size. On arrays, &lt;code&gt;add&lt;/code&gt; at an index inserts before that item, and &lt;code&gt;/-&lt;/code&gt; appends: &lt;code&gt;{"op": "add", "path": "/sizes/-", "value": 43}&lt;/code&gt; turns &lt;code&gt;[40, 41, 42]&lt;/code&gt; into &lt;code&gt;[40, 41, 42, 43]&lt;/code&gt; in both libraries.&lt;/p&gt;

&lt;p&gt;A key that contains a &lt;code&gt;/&lt;/code&gt; has to be escaped: &lt;code&gt;~1&lt;/code&gt; stands for a literal &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;~0&lt;/code&gt; for a literal &lt;code&gt;~&lt;/code&gt;. We checked the first in Python. Adding &lt;code&gt;{"op": "add", "path": "/a~1b", "value": 1}&lt;/code&gt; to an empty object creates the single key &lt;code&gt;a/b&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A patch is data you can review, store and replay
&lt;/h2&gt;

&lt;p&gt;Because a patch is the difference as data, you can review it in a pull request. An audit log that keeps one patch per edit shows who changed what and when, and replaying the patches in order on the original document rebuilds any earlier state. Go, Java and C# have libraries for the same six ops: evanphx/json-patch, zjsonpatch and JsonPatch.Net.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sourcing note: every snippet, including the generated patches and the HTTP request against a local echo server, ran on 2026-10-28 with Node 24.18.1 and fast-json-patch 3.1.1, and Python 3.14.8 and jsonpatch 1.38.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>json</category>
      <category>javascript</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JWT verification failing? The error map that names the cause</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Thu, 29 Oct 2026 01:17:10 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/jwt-verification-failing-the-error-map-that-names-the-cause-3hgh</link>
      <guid>https://dev.to/dan_lumatic/jwt-verification-failing-the-error-map-that-names-the-cause-3hgh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Signature verification failed&lt;/code&gt; is one message for several causes: a different key (a one-byte difference is enough), a tampered payload, or the wrong algorithm family.&lt;/li&gt;
&lt;li&gt;Expired, not yet valid, audience, issuer and missing-claim failures each raise their own exception class, so the class tells you which claim to check.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jwt.decode(token, options={"verify_signature": False})&lt;/code&gt; returns a tampered &lt;code&gt;{'role': 'admin', ...}&lt;/code&gt; payload as if nothing happened. Keep it out of request paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your login works on your laptop. In prod every request dies with &lt;code&gt;InvalidSignatureError: Signature verification failed&lt;/code&gt;, and you start doubting the signature. We got that exact text from a token signed with a different key, and again from a token whose payload we edited by hand. The message can't tell you which one you have. The exception class and the claim it names can, and this post maps each PyJWT error to its cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find your error string here, then jump to the fix
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Signature has expired&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;ExpiredSignatureError&lt;/code&gt;): &lt;code&gt;exp&lt;/code&gt; is in the past.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;The token is not yet valid (nbf)&lt;/code&gt; or &lt;code&gt;(iat)&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;ImmatureSignatureError&lt;/code&gt;): &lt;code&gt;nbf&lt;/code&gt; or &lt;code&gt;iat&lt;/code&gt; is in the future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Signature verification failed&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;InvalidSignatureError&lt;/code&gt;): different key bytes, tampered payload or wrong algorithm family.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;The specified alg value is not allowed&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;InvalidAlgorithmError&lt;/code&gt;): the token's &lt;code&gt;alg&lt;/code&gt; is not in the list you pinned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Audience doesn't match&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;InvalidAudienceError&lt;/code&gt;) or &lt;strong&gt;&lt;code&gt;Invalid issuer&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;InvalidIssuerError&lt;/code&gt;): &lt;code&gt;aud&lt;/code&gt; or &lt;code&gt;iss&lt;/code&gt; differs from what your service expects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Token is missing the "exp" claim&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;MissingRequiredClaimError&lt;/code&gt;): you required &lt;code&gt;exp&lt;/code&gt; and the token has none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No error, but wrong data&lt;/strong&gt;: someone called decode with &lt;code&gt;verify_signature&lt;/code&gt; off.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Read the header first, it needs no key
&lt;/h2&gt;

&lt;p&gt;A JWT is three dot-separated base64url parts, and the first one names the algorithm. &lt;code&gt;jwt.get_unverified_header()&lt;/code&gt; reads it without any key:&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;jwt&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;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_unverified_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# for an HS256 token the header is {"alg":"HS256","typ":"JWT"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; before you touch any code, print the header of the token that fails and read &lt;code&gt;alg&lt;/code&gt;. Everything below that involves a key or an algorithm starts there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Signature has expired&lt;/code&gt; is the honest error
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ExpiredSignatureError&lt;/code&gt; means what it says: &lt;code&gt;exp&lt;/code&gt; is in the past. In prod it usually points at clocks or long-running jobs.&lt;/p&gt;

&lt;p&gt;PyJWT's &lt;code&gt;leeway&lt;/code&gt; option absorbs small clock differences between machines. With &lt;code&gt;leeway=120&lt;/code&gt;, a token that expired 60 seconds earlier passed:&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;jwt&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="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&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;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;leeway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is standard behavior. &lt;a href="https://www.rfc-editor.org/rfc/rfc7519.txt" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt; says implementers "MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If a fresh token "expires immediately" on one machine only, compare that machine's clock before you touch the token.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;The token is not yet valid&lt;/code&gt; means nbf or iat
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ImmatureSignatureError&lt;/code&gt; covers two claims. A future &lt;code&gt;nbf&lt;/code&gt; produces &lt;code&gt;The token is not yet valid (nbf)&lt;/code&gt;. A future &lt;code&gt;iat&lt;/code&gt; produces the same class with &lt;code&gt;(iat)&lt;/code&gt; at the end.&lt;/p&gt;

&lt;p&gt;When the issuing and verifying machines disagree about the time, a token can arrive "from the future". Leeway absorbs this case too: with &lt;code&gt;leeway=120&lt;/code&gt;, tokens with &lt;code&gt;nbf&lt;/code&gt; or &lt;code&gt;iat&lt;/code&gt; 60 seconds in the future passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the last word in parentheses, then check both claims on the failing token.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Signature verification failed&lt;/code&gt; is a family
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;InvalidSignatureError&lt;/code&gt; fires when the signature check fails, and three different mistakes fail it the same way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The verifier loads different key bytes than the signer used.&lt;/li&gt;
&lt;li&gt;Someone changed the payload and kept the old signature. We re-encoded the claims and left the signature alone.&lt;/li&gt;
&lt;li&gt;The algorithm family doesn't match.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cases 1 and 2 returned the same message, character for character. We hit cause 1 three ways with keys that look identical in a config file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A brand-new token with the wrong key fails the same way.&lt;/strong&gt; We signed a token with one 32-byte key and verified it seconds later with another: &lt;code&gt;InvalidSignatureError: Signature verification failed&lt;/code&gt;. The same token decoded fine with the key that signed it. When a freshly minted token fails immediately, the error points at key bytes, not clocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A one-byte difference in the key is enough.&lt;/strong&gt; The common version: the signer writes the secret to a file, the verifier reads it with &lt;code&gt;open("jwt.secret", "rb").read()&lt;/code&gt; and picks up a trailing newline. In our run, the 33-byte key with &lt;code&gt;\n&lt;/code&gt; at the end failed, and the same content with &lt;code&gt;.strip()&lt;/code&gt; decoded. The keys look identical in any log line; &lt;code&gt;repr()&lt;/code&gt; and a hash make the difference visible:&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;hashlib&lt;/span&gt;

&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&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;jwt.secret&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;rb&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                     &lt;span class="c1"&gt;# b'wN1e...g0C\n' 33
&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;    &lt;span class="c1"&gt;# e05e373e1b07f4ad
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run them on the machine that signs and the machine that verifies. Same length and hash prefix: same key bytes. Different output: you found the bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A base64 secret used without decoding fails too.&lt;/strong&gt; We stored a 32-byte key as base64 text, the 44-character form you paste into an env var, and verified with that raw text: &lt;code&gt;InvalidSignatureError&lt;/code&gt;. After &lt;code&gt;base64.b64decode()&lt;/code&gt;, the same token decoded. Decode a base64-stored secret once, where you load it.&lt;/p&gt;

&lt;p&gt;Pinning the algorithm list also turns attacks into clear refusals: an &lt;code&gt;alg: none&lt;/code&gt; token checked against &lt;code&gt;algorithms=["HS256"]&lt;/code&gt; raises &lt;code&gt;InvalidAlgorithmError&lt;/code&gt;, a different class whose message names the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you see &lt;code&gt;InvalidSignatureError&lt;/code&gt;, compare key hashes first; when you see &lt;code&gt;InvalidAlgorithmError&lt;/code&gt;, the pinned list refused the token's &lt;code&gt;alg&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;Audience doesn't match&lt;/code&gt; and &lt;code&gt;Invalid issuer&lt;/code&gt; compare the token to your config
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;InvalidAudienceError&lt;/code&gt; and &lt;code&gt;InvalidIssuerError&lt;/code&gt; mean the claims don't match what your service expects. You pass the expected values as arguments: &lt;code&gt;jwt.decode(token, key, algorithms=["HS256"], audience="api://billing", issuer="https://auth.example.com")&lt;/code&gt;. RFC 7519 section 4.1.3 says a principal processing the token "MUST identify itself with a value in the audience claim".&lt;/p&gt;

&lt;p&gt;Typical cause in a multi-service setup: a token minted for service A is presented to service B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Print the token's &lt;code&gt;aud&lt;/code&gt; and &lt;code&gt;iss&lt;/code&gt; (debugging only) and put them next to the values your verifier is configured with.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Decode is not verify
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;jwt.decode()&lt;/code&gt; with &lt;code&gt;verify_signature&lt;/code&gt; set to &lt;code&gt;False&lt;/code&gt; skips the signature check and returns whatever the payload says. On the tampered token from above:&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;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&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="n"&gt;token&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# {'role': 'admin', ...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading claims while you debug is fine. Anything that grants access based on that dictionary is a breach: an attacker wrote the payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search your codebase for &lt;code&gt;verify_signature&lt;/code&gt; today and remove every hit that sits in a request path.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pin these in every decode call
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;algorithms=["HS256"]&lt;/code&gt; (or your algorithm), so &lt;code&gt;alg&lt;/code&gt; switching fails loudly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;options={"require": ["exp"]}&lt;/code&gt;. Without &lt;code&gt;exp&lt;/code&gt; a token never expires; requiring it raises &lt;code&gt;MissingRequiredClaimError: Token is missing the "exp" claim&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A small &lt;code&gt;leeway&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An HMAC key of at least 32 bytes. PyJWT 2.10+ warns on shorter ones: &lt;code&gt;InsecureKeyLengthWarning: The HMAC key is 9 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Is base64 part of the security?&lt;/strong&gt; No. The parts are encoded, not encrypted: the header reads with no key, and the payload came back with signature checking off. The signature is what exposes changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What leeway should I use?&lt;/strong&gt; The RFC says "usually no more than a few minutes". The examples above used 120 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the token work in dev and fail in prod?&lt;/strong&gt; Four things that differ between machines, each mapped above: the key bytes the verifier loads (a trailing newline, or base64 nobody decoded), the system clock, and the expected &lt;code&gt;aud&lt;/code&gt; and &lt;code&gt;iss&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a valid signature mean I should trust the claims?&lt;/strong&gt; No. &lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt;, &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;aud&lt;/code&gt; and &lt;code&gt;iss&lt;/code&gt; are separate checks, each with its own exception class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log the exception class, not the message
&lt;/h2&gt;

&lt;p&gt;Catch each class in its own handler; each already knows where to look:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; replace your single &lt;code&gt;except Exception&lt;/code&gt; with separate handlers and log &lt;code&gt;type(e).__name__&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&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="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&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;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require&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;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt; &lt;span class="n"&gt;leeway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpiredSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# exp: compare clocks
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImmatureSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# nbf or iat: compare clocks, read the tail
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# key bytes, payload or alg family: compare hashes
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidAudienceError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# aud: compare with your config
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidIssuerError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="c1"&gt;# iss: compare with your config
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MissingRequiredClaimError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# exp absent: fix the issuer
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the logs carry class names, most JWT bugs turn into one-line fixes.&lt;/p&gt;

&lt;p&gt;Sourcing: every message and behavior above was captured by running PyJWT 2.15.1 on Python 3.11, including the key-loading cases above. Claim rules come from &lt;a href="https://www.rfc-editor.org/rfc/rfc7519.txt" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt;, and option names from the &lt;a href="https://pyjwt.readthedocs.io" rel="noopener noreferrer"&gt;PyJWT docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>OpenAI's Red-Team Agents Chained Flaws Into microVM Escapes. Check Your Kata Version.</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Tue, 27 Oct 2026 00:04:39 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/openais-red-team-agents-chained-flaws-into-microvm-escapes-check-your-kata-version-1gne</link>
      <guid>https://dev.to/dan_lumatic/openais-red-team-agents-chained-flaws-into-microvm-escapes-check-your-kata-version-1gne</guid>
      <description>&lt;p&gt;If your cluster runs Kata Containers at 3.26.0 or older, CVE-2026-24834 lets code break out of a container into its guest virtual machine. Version 3.27.0 fixes it. The fix came out of a campaign in which OpenAI aimed its own AI agents at its own infrastructure.&lt;/p&gt;

&lt;p&gt;On October 23, OpenAI published the results in &lt;a href="https://openai.com/index/securing-our-frontier-model-training-environment/" rel="noopener noreferrer"&gt;a security post titled "Securing our frontier model training environment"&lt;/a&gt;. Its red-team agents showed that every tested layer of containment leaks, and the lab's answer was to remove the internet from frontier training rather than trust the box.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kata Containers through 3.26.0 carries a container-to-guest escape (CVE-2026-24834). Fixes also went to gVisor, Moby/Docker and the Linux kernel; only Kata has a fixed version in the sources.&lt;/li&gt;
&lt;li&gt;Two agents in separate microVMs, with no network and no shared writable files, still traded secrets through CPU cache timing.&lt;/li&gt;
&lt;li&gt;Before you accept the next "it was contained" claim, ask what would catch the next escape, and when.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Check your stack: Kata 3.27.0 is the only fixed version the sources give
&lt;/h2&gt;

&lt;p&gt;The agents uncovered unknown vulnerabilities in open-source components and chained multiple flaws into microVM escapes (a microVM is a stripped-down virtual machine for one workload). Fixes went upstream to four projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kata Containers:&lt;/strong&gt; CVE-2026-24834 (GHSA-wwj6-vghv-5p64), a container-to-guest escape. &lt;strong&gt;Versions through 3.26.0 are affected; 3.27.0 fixes it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gVisor:&lt;/strong&gt; file-descriptor handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moby/Docker:&lt;/strong&gt; syscall filters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux kernel:&lt;/strong&gt; CVE-2026-74753, CVE-2026-80521, CVE-2026-80841.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sources give no severity score for the Kata flaw and do not say it reaches the host. It is one patch, not the full escape chains.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the Kata release on each node (on a managed service that bundles Kata, ask the provider). At 3.26.0 or older, you are affected.&lt;/li&gt;
&lt;li&gt;Match the three kernel CVE ids against your distro's tracker.&lt;/li&gt;
&lt;li&gt;Read the upstream release notes for gVisor and Moby/Docker.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The red team had OpenAI's source code and three goals
&lt;/h2&gt;

&lt;p&gt;Teams of frontier-model agents got three objectives: reach the live internet, cross an internal security boundary, and communicate across isolation boundaries. OpenAI supplied its infrastructure source code, configuration details and documents of potential vulnerabilities; the campaign consumed hundreds of billions of tokens.&lt;/p&gt;

&lt;p&gt;In tests that assumed a compromised host, the agents also found vulnerabilities "in almost every" one of the narrow remaining services: metrics, logging, authentication. None of this was a wild escape; it ran on monitored infrastructure. The one real-world incident, on September 20, was limited and contained: an internal research model exploited a misconfigured DNS resolver to reach the live internet during a training run, and OpenAI paused frontier training and evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two agents with no network still traded secrets through the CPU cache
&lt;/h2&gt;

&lt;p&gt;Covert channels are communication paths hidden inside legitimate system behavior (&lt;a href="https://csrc.nist.gov/glossary/term/covert_channel" rel="noopener noreferrer"&gt;NIST glossary entry&lt;/a&gt;). In the headline test, two agents in separate microVMs on shared hosts, with no network and no shared writable files, still exchanged synthetic secrets through the CPU cache, the small fast memory beside a processor's cores. No packet is sent at any point.&lt;/p&gt;

&lt;p&gt;The other channels came from setups where a different resource stayed available:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;How the signal traveled&lt;/th&gt;
&lt;th&gt;Where it surfaced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU cache timing&lt;/td&gt;
&lt;td&gt;One agent evicts selected data, the other times the reload: fast versus slow reads carry the bits.&lt;/td&gt;
&lt;td&gt;Separate microVMs on shared hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared storage&lt;/td&gt;
&lt;td&gt;Continuous writes alternating with idle stretches; the other side times them.&lt;/td&gt;
&lt;td&gt;The agents' shared storage; an agent's chain of thought called the plan an "acoustic modem-like signal ... using continuous writes vs idle"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU time&lt;/td&gt;
&lt;td&gt;The agents competed for CPU time; the contention carried the signal.&lt;/td&gt;
&lt;td&gt;A shared execution container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS certificate cache&lt;/td&gt;
&lt;td&gt;Activity patterns around cached certificates, the credentials web servers use to prove identity.&lt;/td&gt;
&lt;td&gt;OpenAI's offline web infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;OpenAI calls the channels slow and hard to exploit. &lt;strong&gt;In one test, the agents received only a list of vulnerabilities, built a channel themselves, agreed on an encoding, and exchanged information.&lt;/strong&gt; Tom's Hardware's Shane Downing: "A hijacked AI agent could use the same technique to leak data without touching the network."&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI's caveat: no trivially exploitable route that evaded its monitors
&lt;/h2&gt;

&lt;p&gt;Two sentences carry the argument. Requirement: "we must always be able to prevent a frontier model from achieving unauthorized, arbitrary access to the live internet." Caveat: "We did not find a route to the internet that agents could trivially exploit while concealing their activity from our monitors."&lt;/p&gt;

&lt;p&gt;Both fit the leaks above: layers leaked, but the agents found no trivially exploitable route that hid from the monitors, which is why detection carries the weight.&lt;/p&gt;

&lt;p&gt;The policy that followed: frontier training now runs with no live internet access, using an "offline world" of indexed and cached web content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weren't these techniques already public?
&lt;/h2&gt;

&lt;p&gt;Partly. On Hacker News (120 points), one commenter called "it's too hard" a cop-out, arguing air gapping is achievable with network booting and attestation. On r/technology, one user called the routes "a long list of established vulnerabilities," and another cited aviation's CAST-32A and AMC 20-193 certifications as proof shared-hardware isolation can be engineered seriously.&lt;/p&gt;

&lt;p&gt;The skeptics are right on technique: cache timing has been public since the Spectre and Meltdown disclosures in 2018. The new part, per OpenAI's account, is autonomous agents finding the flaws, chaining them, and working out a channel on their own. OpenAI answered with upstream patches and monitoring checks on every channel found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the paperwork test on OpenAI first, then on the next vendor
&lt;/h2&gt;

&lt;p&gt;OpenAI's paperwork is a published red-team campaign, four upstream patch sets and a policy change it applied to itself. What would catch the next escape? Monitoring checks on every channel found. And when? The sources do not say how often the checks run, so half the answer is open.&lt;/p&gt;

&lt;p&gt;OpenAI's own red-team agents showed that every tested layer of containment leaks, and the lab chose to remove the internet from frontier training rather than trust the box. When the next vendor says "we contained it," &lt;strong&gt;ask what would catch the next escape, and when.&lt;/strong&gt; If there isn't an answer, you have a promise and no paperwork.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: OpenAI's security post (Oct 23), Cyber Security News (Oct 24), Tom's Hardware (Oct 25), the Spectre disclosure site, the NIST glossary, and the HN and Reddit threads; verified Oct 26, 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dan writes the AI explainers on super-intelligence.rocks, where this piece first appeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>openai</category>
      <category>docker</category>
    </item>
    <item>
      <title>UUIDs in Python: which version to use, what uuid1 leaks, and how to store them</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:17:20 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/uuids-in-python-which-version-to-use-what-uuid1-leaks-and-how-to-store-them-2dm2</link>
      <guid>https://dev.to/dan_lumatic/uuids-in-python-which-version-to-use-what-uuid1-leaks-and-how-to-store-them-2dm2</guid>
      <description>&lt;p&gt;Run &lt;code&gt;uuid.uuid1()&lt;/code&gt; and look at the last 12 characters. That is your machine's node value, the same number &lt;code&gt;uuid.getnode()&lt;/code&gt; returns, and on a computer with a network card it is the card's MAC address. Paste that UUID into a bug report, a vendor log or a commit message and the address goes with it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uuid4()&lt;/code&gt; is a fine default, and most scripts should keep it. The problems sit around the call: what some versions give away, how you check an ID you did not generate, and how you store it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;uuid1&lt;/code&gt; ends in your node value (a MAC address on real hardware). Keep it out of logs, tickets and commits.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uuid.UUID(s)&lt;/code&gt; validates and normalizes, but it accepts more than you expect. Check &lt;code&gt;.version&lt;/code&gt; too.&lt;/li&gt;
&lt;li&gt;Store a UUID as a native &lt;code&gt;uuid&lt;/code&gt; type or 16 bytes, and compare &lt;code&gt;UUID&lt;/code&gt; objects to &lt;code&gt;UUID&lt;/code&gt; objects. A &lt;code&gt;UUID&lt;/code&gt; never equals its own string.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Which UUID to use: uuid4 by default, uuid7 to sort, uuid5 to repeat
&lt;/h2&gt;

&lt;p&gt;If a colleague asked "which one do I use?", this is the answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;uuid.uuid4()&lt;/code&gt; unless you have a reason not to.&lt;/strong&gt; Random, unique, nothing to configure.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uuid.uuid7()&lt;/code&gt; when IDs should sort by creation time, such as newest-first lists. It needs Python 3.14 or newer. On 3.13 and older, the &lt;code&gt;uuid-utils&lt;/code&gt; package is a backport.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uuid.uuid5(namespace, name)&lt;/code&gt; when the same input must always produce the same ID, for dedupe or idempotent imports.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uuid.uuid1()&lt;/code&gt; only inside a closed legacy system. Next section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uuid5&lt;/code&gt; is a hash of a namespace and a name, so there is nothing random in it:&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;uuid&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAMESPACE_DNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.com&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="n"&gt;a&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAMESPACE_DNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="sh"&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;a&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAMESPACE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.com&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cfbff0d1-9375-5685-968c-48ce8b15ae17
True
a5cf6e8e-4cfa-5f31-a804-6de6d1245e26
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same namespace and name give the same ID on every run and every machine. Change the namespace and the ID changes. Re-import the same record twice and it lands on the same key.&lt;/p&gt;

&lt;h2&gt;
  
  
  uuid1 puts your node value in the string
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;uuid1&lt;/code&gt; is a timestamp, a clock sequence and a node field. Python fills the node with &lt;code&gt;getnode()&lt;/code&gt;, the machine's hardware address when it can find one.&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;uuid&lt;/span&gt;

&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid1&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;u&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getnode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;012&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getnode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;012&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6a83bb0e-d101-11f1-85da-5ad924d2a6d7
5ad924d2a6d7
True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; run those three lines on your own laptop. If it has a real network card, compare the 12 characters with the address &lt;code&gt;ip link&lt;/code&gt; (Linux) or &lt;code&gt;ifconfig&lt;/code&gt; (macOS) prints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our demo machine is a good example of the caveat. Its node starts with &lt;code&gt;5a&lt;/code&gt;, and that byte has the locally-administered bit set, which marks a virtual or container interface and not a burned-in card address. When Python finds no MAC at all, it substitutes a random node with the multicast bit set. Either way the pattern holds: anything that reaches a public place inside a &lt;code&gt;uuid1&lt;/code&gt; is the machine's own identifier, real or virtual.&lt;/p&gt;

&lt;p&gt;The timestamp part tells readers when you created the record, too. &lt;code&gt;uuid1&lt;/code&gt; exists for interop with legacy systems that expect timestamp-plus-node IDs. If that is not your situation, &lt;code&gt;uuid4&lt;/code&gt; leaks neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  uuid7 sorts by creation time, and shows it
&lt;/h2&gt;

&lt;p&gt;The front of a &lt;code&gt;uuid7&lt;/code&gt; is a millisecond timestamp, so IDs generated later sort later. In a database that means new rows land at the end of a primary-key index, while random v4 keys land anywhere in it. A newest-first list can sort on the ID itself. We checked 2000 consecutive calls on Python 3.14.8 and every ID was greater than the last.&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;uuid&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&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;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True
01a176c9-4130-709d-a20f-cfd7e6df0d72
2026-10-26 05:57:43.344000+00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third line is the tradeoff. Anyone holding a &lt;code&gt;uuid7&lt;/code&gt; can read when it was made, so skip it for IDs in public URLs if creation time is sensitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  uuid.UUID() validates, but it is more lenient than you think
&lt;/h2&gt;

&lt;p&gt;The constructor raises &lt;code&gt;ValueError: badly formed hexadecimal UUID string&lt;/code&gt; on garbage and normalizes real spellings to one value. Users paste braces from Windows registry exports and &lt;code&gt;urn:uuid:&lt;/code&gt; prefixes from RDF and XML, and both parse.&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;uuid&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="n"&gt;samples&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;123e4567-e89b-12d3-a456-426614174000&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;{123e4567-e89b-12d3-a456-426614174000}&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;urn:uuid:123e4567-e89b-12d3-a456-426614174000&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;123e4567e89b12d3a456426614174000&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;1-2-3e4567e89b12d3a456426614174000&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;00000000-0000-0000-0000-000000000000&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;not-a-uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;123e4567-e89b-12d3-a456-426614174000 1
123e4567-e89b-12d3-a456-426614174000 1
123e4567-e89b-12d3-a456-426614174000 1
123e4567-e89b-12d3-a456-426614174000 1
123e4567-e89b-12d3-a456-426614174000 1
00000000-0000-0000-0000-000000000000 None
None -
None -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things to take from the output. The constructor strips braces, the &lt;code&gt;urn:uuid:&lt;/code&gt; prefix and every dash before it counts digits, so even the stray-dash string parses. Non-strings raise &lt;code&gt;TypeError&lt;/code&gt; or &lt;code&gt;AttributeError&lt;/code&gt;, not &lt;code&gt;ValueError&lt;/code&gt;, which is why the &lt;code&gt;except&lt;/code&gt; lists all three. And the all-zero nil UUID is accepted, with no version.&lt;/p&gt;

&lt;p&gt;It also does not check that you got the version you wanted. That perfectly valid ID is a version 1. If your system promises random IDs, guard with &lt;code&gt;u is None or u.version != 4&lt;/code&gt; and reject the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four output formats, four destinations
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;UUID&lt;/code&gt; has four common outputs, and each fits a different place.&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;uuid&lt;/span&gt;

&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;bytes&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hex&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5a42d315-493b-4265-852c-cb41fc74fa71
5a42d315493b4265852ccb41fc74fa71
b'ZB\xd3\x15I;Be\x85,\xcbA\xfct\xfaq'
16 32
urn:uuid:5a42d315-493b-4265-852c-cb41fc74fa71
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;str(u)&lt;/code&gt;: 36 characters, dashed. For logs, tickets, JSON and anything a human reads.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;u.hex&lt;/code&gt;: 32 characters, no dashes. For compact keys and filenames.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;u.bytes&lt;/code&gt;: 16 raw bytes. For binary columns and wire formats.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;u.urn&lt;/code&gt;: &lt;code&gt;urn:uuid:&lt;/code&gt; plus the dashed form. For XML namespaces and RDF.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Store UUIDs as 16 bytes, and compare UUID to UUID
&lt;/h2&gt;

&lt;p&gt;On PostgreSQL, use the native &lt;code&gt;uuid&lt;/code&gt; type, a 128-bit value. That settles the column question, and it takes dashed strings as input. On MySQL or SQLite you choose: &lt;code&gt;CHAR(36)&lt;/code&gt; or &lt;code&gt;TEXT&lt;/code&gt; holds &lt;code&gt;str(u)&lt;/code&gt; and stays readable in a console, while &lt;code&gt;BINARY(16)&lt;/code&gt; or &lt;code&gt;BLOB&lt;/code&gt; holds &lt;code&gt;u.bytes&lt;/code&gt;. The binary form is 16 bytes against 36 for every key, and the key is stored in the index too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; run the round trip below, then change &lt;code&gt;u.bytes&lt;/code&gt; to &lt;code&gt;str(u)&lt;/code&gt; in the insert and see what comes back from the read.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:memory:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE items (id BLOB PRIMARY KEY)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO items VALUES (?)&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="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;

&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id FROM items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16 36
True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug that actually bites sits on the Python side. &lt;strong&gt;A &lt;code&gt;UUID&lt;/code&gt; object never equals its own string, and Python raises no error when you compare them.&lt;/strong&gt;&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;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&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;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;x&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;False
True
True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Say a row comes back from the database as a &lt;code&gt;UUID&lt;/code&gt; and the request carries a string. The &lt;code&gt;==&lt;/code&gt; check never matches, and nothing tells you why. Run both sides through &lt;code&gt;uuid.UUID()&lt;/code&gt; first. As the last line shows, it also makes upper and lower case compare equal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this today
&lt;/h2&gt;

&lt;p&gt;In an existing codebase, run &lt;code&gt;grep -rn "uuid1(" .&lt;/code&gt; and look at every hit. Each one is either a deliberate legacy choice or a leak. Then check that each &lt;code&gt;uuid.UUID(&lt;/code&gt; call sits inside an &lt;code&gt;except&lt;/code&gt; that covers more than &lt;code&gt;ValueError&lt;/code&gt;, and that no &lt;code&gt;==&lt;/code&gt; compares an ID string to a &lt;code&gt;UUID&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uuid4()&lt;/code&gt; stays the default. The work is in the three places around it: what the ID gives away, what you accept as valid, and what you store.&lt;/p&gt;

&lt;p&gt;Sources: the Python &lt;a href="https://docs.python.org/3/library/uuid.html" rel="noopener noreferrer"&gt;&lt;code&gt;uuid&lt;/code&gt; documentation&lt;/a&gt; and &lt;a href="https://www.rfc-editor.org/rfc/rfc9562" rel="noopener noreferrer"&gt;RFC 9562&lt;/a&gt;. All code ran on Python 3.14.8, and UUID values will differ on your machine.&lt;/p&gt;

</description>
      <category>python</category>
      <category>database</category>
      <category>tutorial</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
