<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: mypy</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/mypy.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-05-07T18:37:33+00:00</updated><author><name>Simon Willison</name></author><entry><title>astral-sh/ty</title><link href="https://simonwillison.net/2025/May/7/ty/#atom-tag" rel="alternate"/><published>2025-05-07T18:37:33+00:00</published><updated>2025-05-07T18:37:33+00:00</updated><id>https://simonwillison.net/2025/May/7/ty/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/astral-sh/ty"&gt;astral-sh/ty&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Astral have been working on this "extremely fast Python type checker and language server, written in Rust" &lt;a href="https://simonwillison.net/2025/Jan/29/charlie-marsh/"&gt;quietly but in-the-open&lt;/a&gt; for a while now. Here's the first alpha public release - albeit &lt;a href="https://news.ycombinator.com/item?id=43918484#43919354"&gt;not yet announced&lt;/a&gt; - as &lt;a href="https://pypi.org/project/ty/"&gt;ty&lt;/a&gt; on PyPI (nice &lt;a href="https://news.ycombinator.com/item?id=43918484#43920112"&gt;donated&lt;/a&gt; two-letter name!)&lt;/p&gt;
&lt;p&gt;You can try it out via &lt;a href="https://docs.astral.sh/uv/guides/tools/#running-tools"&gt;uvx&lt;/a&gt; like this - run the command in a folder full of Python code and see what comes back:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx ty check
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I got zero errors for my recent, simple &lt;a href="https://github.com/simonw/condense-json"&gt;condense-json&lt;/a&gt; library and a &lt;em&gt;ton&lt;/em&gt; of errors for my more mature &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils&lt;/a&gt; library - &lt;a href="https://gist.github.com/simonw/a13e1720b03e23783ae668eca7f6f12a"&gt;output here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It really is &lt;em&gt;fast&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /tmp
git clone https://github.com/simonw/sqlite-utils
cd sqlite-utils
time uvx ty check
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reports it running in around a tenth of a second (0.109 total wall time) using multiple CPU cores:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx ty check  0.18s user 0.07s system 228% cpu 0.109 total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;time uvx mypy .&lt;/code&gt; in the same folder (both after first ensuring the underlying tools had been cached) took around 7x longer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx mypy .  0.46s user 0.09s system 74% cpu 0.740 total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't a fair comparison yet as ty still isn't feature complete in comparison to mypy.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=43918484"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/astral"&gt;astral&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ty"&gt;ty&lt;/a&gt;&lt;/p&gt;



</summary><category term="pypi"/><category term="python"/><category term="rust"/><category term="mypy"/><category term="uv"/><category term="astral"/><category term="ty"/></entry><entry><title>Compiling Black with mypyc</title><link href="https://simonwillison.net/2022/May/31/compiling-black-with-mypyc/#atom-tag" rel="alternate"/><published>2022-05-31T23:24:16+00:00</published><updated>2022-05-31T23:24:16+00:00</updated><id>https://simonwillison.net/2022/May/31/compiling-black-with-mypyc/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ichard26.github.io/blog/2022/05/31/compiling-black-with-mypyc-part-1/"&gt;Compiling Black with mypyc&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Richard Si is a Black contributor who recently obtained a 2x performance boost by compiling Black using the mypyc tool from the mypy project, which uses Python type annotations to generate a compiled C version of the Python logic. He wrote up this fantastic three-part series describing in detail how he achieved this, including plenty of tips on Python profiling and clever optimization tricks.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/llanga/status/1531741163539005449"&gt;Łukasz Langa&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/performance"&gt;performance&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/black"&gt;black&lt;/a&gt;&lt;/p&gt;



</summary><category term="performance"/><category term="python"/><category term="mypy"/><category term="black"/></entry><entry><title>typesplainer</title><link href="https://simonwillison.net/2022/Mar/15/typesplainer/#atom-tag" rel="alternate"/><published>2022-03-15T06:18:40+00:00</published><updated>2022-03-15T06:18:40+00:00</updated><id>https://simonwillison.net/2022/Mar/15/typesplainer/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://pypi.org/project/typesplainer/"&gt;typesplainer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A Python module that produces human-readable English descriptions of Python type definitions—also available as a web interface.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/willmcgugan/status/1503020152471769093"&gt;@willmcgugan&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="mypy"/></entry><entry><title>Mypyc</title><link href="https://simonwillison.net/2022/Jan/30/mypyc/#atom-tag" rel="alternate"/><published>2022-01-30T01:31:12+00:00</published><updated>2022-01-30T01:31:12+00:00</updated><id>https://simonwillison.net/2022/Jan/30/mypyc/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://mypyc.readthedocs.io/en/latest/introduction.html"&gt;Mypyc&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Spotted this in the Black release notes: “Black is now compiled with mypyc for an overall 2x speed-up”. Mypyc is a tool that compiles Python modules (written in a subset of Python) to C extensions—similar to Cython but using just Python syntax, taking advantage of type annotations to perform type checking and type inference. It’s part of the mypy type checking project, which has been using it since 2019 to gain a 4x performance improvement over regular Python.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://black.readthedocs.io/en/stable/change_log.html#id1"&gt;Black release notes&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/c"&gt;c&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/performance"&gt;performance&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;&lt;/p&gt;



</summary><category term="c"/><category term="performance"/><category term="python"/><category term="mypy"/></entry><entry><title>TypeScript for Pythonistas</title><link href="https://simonwillison.net/2021/Dec/17/typescript-for-pythonistas/#atom-tag" rel="alternate"/><published>2021-12-17T19:43:17+00:00</published><updated>2021-12-17T19:43:17+00:00</updated><id>https://simonwillison.net/2021/Dec/17/typescript-for-pythonistas/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/@Pilot-EPD-Blog/typescript-for-pythonistas-f90bbb297f0a"&gt;TypeScript for Pythonistas&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Really useful explanation of how TypeScript differs from Python with mypy. I hadn’t realized TypeScript leans so far into structural typing, to the point that two types with different names but the same “shape” are identified as being the same type as each other.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=29574948"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/typescript"&gt;typescript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="typescript"/><category term="mypy"/></entry><entry><title>Tests aren’t enough: Case study after adding type hints to urllib3</title><link href="https://simonwillison.net/2021/Oct/18/case-study-after-adding-type-hints-to-urllib3/#atom-tag" rel="alternate"/><published>2021-10-18T19:03:38+00:00</published><updated>2021-10-18T19:03:38+00:00</updated><id>https://simonwillison.net/2021/Oct/18/case-study-after-adding-type-hints-to-urllib3/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://sethmlarson.dev/blog/2021-10-18/tests-arent-enough-case-study-after-adding-types-to-urllib3"&gt;Tests aren’t enough: Case study after adding type hints to urllib3&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Very thorough write-up by Seth Michael Larson describing what it took for the urllib3 Python library to fully embrace mypy and optional typing and what they learned along the way.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/static-typing"&gt;static-typing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/seth-michael-larson"&gt;seth-michael-larson&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="static-typing"/><category term="mypy"/><category term="seth-michael-larson"/></entry><entry><title>Datasette on Codespaces, sqlite-utils API reference documentation and other weeknotes</title><link href="https://simonwillison.net/2021/Aug/14/datasette-on-codespaces/#atom-tag" rel="alternate"/><published>2021-08-14T04:57:12+00:00</published><updated>2021-08-14T04:57:12+00:00</updated><id>https://simonwillison.net/2021/Aug/14/datasette-on-codespaces/#atom-tag</id><summary type="html">
    &lt;p&gt;This week I &lt;a href="https://datasette.substack.com/p/everything-new-in-datasette-since"&gt;broke my streak&lt;/a&gt; of &lt;em&gt;not&lt;/em&gt; sending out the Datasette newsletter, figured out how to use Sphinx for Python class documentation, worked out how to run Datasette on GitHub Codespaces, implemented Datasette column metadata and got tantalizingly close to a solution for an elusive Datasette feature.&lt;/p&gt;
&lt;h4&gt;API reference documentation for sqlite-utils using Sphinx&lt;/h4&gt;
&lt;p&gt;I've never been a big fan of Javadoc-style API documentation: I usually find that documentation structured around classes and methods fails to show me how to actually use those classes to solve real-world problems. I've tended to avoid it for my own projects.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils Python library&lt;/a&gt; has a ton of functionality, but it mainly boils down to two classes: &lt;code&gt;Database&lt;/code&gt; and &lt;code&gt;Table&lt;/code&gt;. Since it  already has pretty comprehesive narrative documentation explaining the different problems it can solve, I decided to try experimenting with the &lt;a href="https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html"&gt;Sphinx autodoc&lt;/a&gt; module to produce some classic &lt;a href="https://sqlite-utils.datasette.io/en/stable/reference.html"&gt;API reference documentation&lt;/a&gt; for it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the new API reference documentation" src="https://static.simonwillison.net/static/2021/sqlite-utils-api-doc.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Since autodoc works from docstrings, this was also a great excuse to add more comprehensive docstrings and type hints to the library. This helps tools like Jupyter notebooks and VS Code display more useful inline help.&lt;/p&gt;
&lt;p&gt;This proved to be time well spent! Here's what &lt;code&gt;sqlite-utils&lt;/code&gt; looks like in VS Code now:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of VS Code showing inline help for the enable_fts() method" src="https://static.simonwillison.net/static/2021/vs-code-hints.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;mypy&lt;/code&gt; against the type hints also helped me identify and fix a couple of obscure edge-case bugs in the existing methods, detailed in &lt;a href="https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-15-1"&gt;the 3.15.1 release notes&lt;/a&gt;. It's taken me a few years but I'm finally starting to come round to Python's optional typing as being worth the additional effort!&lt;/p&gt;
&lt;p&gt;Figuring out how to use autodoc in Sphinx, and then how to get the documentation to build correctly on &lt;a href="https://readthedocs.org/"&gt;Read The Docs&lt;/a&gt; took some effort. I wrote up what I learned in &lt;a href="https://til.simonwillison.net/sphinx/sphinx-autodoc"&gt;this TIL&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Datasette on GitHub Codespaces&lt;/h4&gt;
&lt;p&gt;GitHub released their new &lt;a href="https://github.com/features/codespaces"&gt;Codespaces&lt;/a&gt; online development environments to general availability this week and I'm really excited about it. I ran a team at Eventbrite for a while resonsible for development environment tooling and it really was shocking how much time and money was lost to broken local development environments, even with a significant amount of engineering effort applied to the problem.&lt;/p&gt;
&lt;p&gt;Codespaces promises a fresh, working development environment on-demand any time you need it. That's a very exciting premise! Their detailed write-up of how they convinced GitHub's own internal engineers to move to it is full of &lt;a href="https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/"&gt;intriguing details&lt;/a&gt; - getting an existing application working with it is no small feat, but the pay-off looks very promising indeed.&lt;/p&gt;
&lt;p&gt;So... I decided to try and get Datasette running on it. It works really well!&lt;/p&gt;
&lt;p&gt;You can run Datasette in any Codespace environment using the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the terminal. Three-bar-menu-icon, View, Terminal does the trick.&lt;/li&gt;
&lt;li&gt;In the terminal run &lt;code&gt;pip install datasette datasette-x-forwarded-host&lt;/code&gt; (more on this in a moment).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;datasette&lt;/code&gt; - Codespaces will automatically setup port forwarding and give you a link to "Open in Browser" - click the link and you're done!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can &lt;code&gt;pip install sqlite-utils&lt;/code&gt; and then use &lt;a href="https://sqlite-utils.datasette.io/en/stable/cli.html#inserting-csv-or-tsv-data"&gt;sqlite-utils insert&lt;/a&gt; to create SQLite databases to use with Datasette.&lt;/p&gt;
&lt;p&gt;There was one catch: the first time I ran Datasette, clicking on any of the internal links within the web application took me to &lt;code&gt;http://localhost/&lt;/code&gt; pages that broke with a 404.&lt;/p&gt;
&lt;p&gt;It turns out the Codespaces proxy sends a &lt;code&gt;host: localhost&lt;/code&gt; header - which Datasette then uses to incorrectly construct internal URLs.&lt;/p&gt;
&lt;p&gt;So I wrote a tiny ASGI plugin, &lt;a href="https://datasette.io/plugins/datasette-x-forwarded-host"&gt;datasette-x-forwarded-host&lt;/a&gt;, which takes the incoming &lt;code&gt;X-Forwarded-Host&lt;/code&gt; provided by Codespaces and uses that as the &lt;code&gt;Host&lt;/code&gt; header within Datasette itself. After that everything worked fine.&lt;/p&gt;
&lt;h4&gt;sqlite-utils insert --flatten&lt;/h4&gt;
&lt;p&gt;Early this week I finally figured out &lt;a href="https://cloud.google.com/run/docs/logging"&gt;Cloud Run logging&lt;/a&gt;. It's actually really good! In doing so, I worked out &lt;a href="https://til.simonwillison.net/cloudrun/tailing-cloud-run-request-logs"&gt;a convoluted recipe&lt;/a&gt; for tailing the JSON logs locally and piping them into a SQLite database so that I could analyze them with Datasette.&lt;/p&gt;
&lt;p&gt;Part of the reason it was convoluted is that Cloud Run logs feature nested JSON, but &lt;a href="https://sqlite-utils.datasette.io/en/stable/cli.html#inserting-json-data"&gt;sqlite-utils insert&lt;/a&gt; only works against an array of flat JSON objects. I had to use &lt;a href="https://til.simonwillison.net/jq/flatten-nested-json-objects-jq"&gt;this jq monstrosity&lt;/a&gt; to flatten the nested JSON into key/value pairs.&lt;/p&gt;
&lt;p&gt;Since I've had to solve this problem a few times now I decided to improve &lt;code&gt;sqlite-utils&lt;/code&gt; to have it do the work instead. You can now use the new &lt;code&gt;--flatten&lt;/code&gt; option like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sqlite-utils insert logs.db logs log.json --flatten
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create a schema that flattens nested objects into a &lt;code&gt;topkey_nextkey&lt;/code&gt; structure like so:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;CREATE TABLE [logs] (
   [httpRequest_latency] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [httpRequest_requestMethod] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [httpRequest_requestSize] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [httpRequest_status] &lt;span class="pl-k"&gt;INTEGER&lt;/span&gt;,
   [insertId] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [labels_service] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;
);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Full &lt;a href="https://sqlite-utils.datasette.io/en/stable/cli.html#flattening-nested-json-objects"&gt;documentation for --flatten&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Datasette column metadata&lt;/h4&gt;
&lt;p&gt;I've been wanting to add this for a while: Datasette's main branch now includes an implementation of &lt;a href="https://docs.datasette.io/en/latest/metadata.html#column-descriptions"&gt;column descriptions metadata&lt;/a&gt; for Datasette tables. This is best illustrated by a screenshot (of &lt;a href="https://latest.datasette.io/fixtures/roadside_attractions"&gt;this live demo&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot showing column metadata displayed both at the top of the Datasette table page and in the context menu that shows up for a column" src="https://static.simonwillison.net/static/2021/column-metadata.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;You can add the following to &lt;code&gt;metadata.yml&lt;/code&gt; (or &lt;code&gt;.json&lt;/code&gt;) to specify descriptions for the columns of a given table:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;&lt;span class="pl-ent"&gt;databases&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;fixtures&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;roadside_attractions&lt;/span&gt;:
      &lt;span class="pl-ent"&gt;columns&lt;/span&gt;:
        &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;The name of the attraction&lt;/span&gt;
        &lt;span class="pl-ent"&gt;address&lt;/span&gt;: &lt;span class="pl-s"&gt;The street address for the attraction&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Column descriptions will be shown in a &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; at the top of the page, and will also be added to the menu that appears when you click on the cog icon at the top of a column.&lt;/p&gt;
&lt;h4 id="column-metadata"&gt;Getting closer to query column metadata, too&lt;/h4&gt;
&lt;p&gt;Datasette lets you execute arbitrary SQL queries, like this one:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;select&lt;/span&gt;
  &lt;span class="pl-c1"&gt;roadside_attractions&lt;/span&gt;.&lt;span class="pl-c1"&gt;name&lt;/span&gt;,
  &lt;span class="pl-c1"&gt;roadside_attractions&lt;/span&gt;.&lt;span class="pl-c1"&gt;address&lt;/span&gt;,
  &lt;span class="pl-c1"&gt;attraction_characteristic&lt;/span&gt;.&lt;span class="pl-c1"&gt;name&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt;
  roadside_attraction_characteristics
  &lt;span class="pl-k"&gt;join&lt;/span&gt; roadside_attractions &lt;span class="pl-k"&gt;on&lt;/span&gt; &lt;span class="pl-c1"&gt;roadside_attractions&lt;/span&gt;.&lt;span class="pl-c1"&gt;pk&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;roadside_attraction_characteristics&lt;/span&gt;.&lt;span class="pl-c1"&gt;attraction_id&lt;/span&gt;
  &lt;span class="pl-k"&gt;join&lt;/span&gt; attraction_characteristic &lt;span class="pl-k"&gt;on&lt;/span&gt; &lt;span class="pl-c1"&gt;attraction_characteristic&lt;/span&gt;.&lt;span class="pl-c1"&gt;pk&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;roadside_attraction_characteristics&lt;/span&gt;.&lt;span class="pl-c1"&gt;characteristic_id&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can &lt;a href="https://latest.datasette.io/fixtures?sql=select%0D%0A++roadside_attractions.name%2C%0D%0A++roadside_attractions.address%2C%0D%0A++attraction_characteristic.name%0D%0Afrom%0D%0A++roadside_attraction_characteristics%0D%0A++join+roadside_attractions+on+roadside_attractions.pk+%3D+roadside_attraction_characteristics.attraction_id%0D%0A++join+attraction_characteristic+on+attraction_characteristic.pk+%3D+roadside_attraction_characteristics.characteristic_id"&gt;try that here&lt;/a&gt;. It returns the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;address&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The Mystery Spot&lt;/td&gt;
&lt;td&gt;465 Mystery Spot Road, Santa Cruz, CA 95065&lt;/td&gt;
&lt;td&gt;Paranormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Winchester Mystery House&lt;/td&gt;
&lt;td&gt;525 South Winchester Boulevard, San Jose, CA 95128&lt;/td&gt;
&lt;td&gt;Paranormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bigfoot Discovery Museum&lt;/td&gt;
&lt;td&gt;5497 Highway 9, Felton, CA 95018&lt;/td&gt;
&lt;td&gt;Paranormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burlingame Museum of PEZ Memorabilia&lt;/td&gt;
&lt;td&gt;214 California Drive, Burlingame, CA 94010&lt;/td&gt;
&lt;td&gt;Museum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bigfoot Discovery Museum&lt;/td&gt;
&lt;td&gt;5497 Highway 9, Felton, CA 95018&lt;/td&gt;
&lt;td&gt;Museum&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The columns it returns have names... but I've long wanted to do more with these results. If I could derive &lt;em&gt;which&lt;/em&gt; source columns each of those output columns were, there are a bunch of interesting things I could do, most notably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the output column is a known foreign key relationship, I could turn it into a hyperlink (as seen on &lt;a href="https://latest.datasette.io/fixtures/roadside_attraction_characteristics"&gt;this table page&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;If the original table column has the new column metadata, I could display that as additional documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The challenge is: given an abitrary SQL query, how can I figure out what the resulting columns are going to be and how to tie those back to the original tables?&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href="https://sqlite.org/forum/forumpost/482abd2e0f119555?t=h"&gt;a hint&lt;/a&gt; from the SQLite forum I'm getting &lt;a href="https://github.com/simonw/datasette/issues/1293"&gt;tantalizingly close&lt;/a&gt; to a solution.&lt;/p&gt;
&lt;p&gt;The trick is to horribly abuse SQLite's &lt;code&gt;explain&lt;/code&gt; output. Here's &lt;a href="https://latest.datasette.io/fixtures?sql=explain+select%0D%0A++roadside_attractions.name%2C%0D%0A++roadside_attractions.address%2C%0D%0A++attraction_characteristic.name%0D%0Afrom%0D%0A++roadside_attraction_characteristics%0D%0A++join+roadside_attractions+on+roadside_attractions.pk+%3D+roadside_attraction_characteristics.attraction_id%0D%0A++join+attraction_characteristic+on+attraction_characteristic.pk+%3D+roadside_attraction_characteristics.characteristic_id"&gt;what it looks like&lt;/a&gt; for the example query above:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;addr&lt;/th&gt;
&lt;th&gt;opcode&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;p2&lt;/th&gt;
&lt;th&gt;p3&lt;/th&gt;
&lt;th&gt;p4&lt;/th&gt;
&lt;th&gt;p5&lt;/th&gt;
&lt;th&gt;comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Init&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;OpenRead&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OpenRead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;OpenRead&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Rewind&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;SeekRowid&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;SeekRowid&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;ResultRow&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Next&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Halt&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Transaction&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Goto&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The magic is on line 12: &lt;code&gt;ResultRow 3 3&lt;/code&gt; means "return a result that spans three columns, starting at register 3" - so that's register 3, 4 and 5. Those three registers are populated by the &lt;code&gt;Column&lt;/code&gt; operations on line 9, 10 and 11 (the register they write into is in the &lt;code&gt;p3&lt;/code&gt; column). Each &lt;code&gt;Column&lt;/code&gt; operation specifies the table (as &lt;code&gt;p1&lt;/code&gt;) and the column index within that table (&lt;code&gt;p2&lt;/code&gt;). And those table references map back to the &lt;code&gt;OpenRead&lt;/code&gt; lines at the start, where &lt;code&gt;p1&lt;/code&gt; is that table register (referered to by &lt;code&gt;Column&lt;/code&gt;) and &lt;code&gt;p1&lt;/code&gt; is the root page of the table within the schema.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;select rootpage, name from sqlite_master where rootpage in (45, 46, 47)&lt;/code&gt; produces &lt;a href="https://latest.datasette.io/fixtures?sql=select+rootpage%2C+name+from+sqlite_master+where+rootpage+in+%2845%2C+46%2C+47%29"&gt;the following&lt;/a&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;rootpage&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;roadside_attractions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;attraction_characteristic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;roadside_attraction_characteristics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Tie all of this together, and it may be possible to use &lt;code&gt;explain&lt;/code&gt; to derive the original tables and columns for each of the outputs of an arbitrary query!&lt;/p&gt;
&lt;p&gt;I was almost ready to declare victory, until I tried running it against a query with an &lt;code&gt;order by column&lt;/code&gt; at the end... and the results no longer matched up.&lt;/p&gt;
&lt;p&gt;You can follow &lt;a href="https://github.com/simonw/datasette/issues/1293#issuecomment-898524057"&gt;my ongoing investigation here&lt;/a&gt; - the short version is that I think I'm going to have to learn to decode a whole bunch more opcodes before I can get this to work.&lt;/p&gt;
&lt;p&gt;This is also a very risk way of attacking this problem. The SQLite &lt;a href="https://www.sqlite.org/opcode.html"&gt;documentation for the bytecode engine&lt;/a&gt; includes the following warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This document describes SQLite internals. The information provided here is not needed for routine application development using SQLite. This document is intended for people who want to delve more deeply into the internal operation of SQLite.&lt;/p&gt;
&lt;p&gt;The bytecode engine is not an API of SQLite. Details about the bytecode engine change from one release of SQLite to the next. Applications that use SQLite should not depend on any of the details found in this document.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So it's pretty clear that this is a highly unsupported way of working with SQLite!&lt;/p&gt;
&lt;p&gt;I'm still tempted to try it though. This feature is very much a nice-to-have: if it breaks and the additional column context stops displaying it's not a critical bug - and hopefully I'll be able to ship a Datasette update that takes into account those breaking SQLite changes relatively shortly afterwards.&lt;/p&gt;
&lt;p&gt;If I can find another, more supported way to solve this I'll jump on it!&lt;/p&gt;
&lt;p&gt;In the meantime, I did use this technque to solve a simpler problem. Datasette extracts &lt;code&gt;:named&lt;/code&gt; parameters from arbitrary SQL queries and turns them &lt;a href="https://latest.datasette.io/fixtures/neighborhood_search?_show_sql=1"&gt;into form fields&lt;/a&gt; - but since it uses a simple regular expression for this it could be confused by things like a literal &lt;code&gt;00:04:05&lt;/code&gt; time string contained in a SQL query.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;explain&lt;/code&gt; output for that query includes the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;addr&lt;/th&gt;
&lt;th&gt;opcode&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;p2&lt;/th&gt;
&lt;th&gt;p3&lt;/th&gt;
&lt;th&gt;p4&lt;/th&gt;
&lt;th&gt;p5&lt;/th&gt;
&lt;th&gt;comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;:text&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So I wrote some code which uses &lt;code&gt;explain&lt;/code&gt; to extract just the &lt;code&gt;p4&lt;/code&gt; operands from &lt;code&gt;Variable&lt;/code&gt; columns and treats those as the extracted parameters! This feels a lot safer than the more complex &lt;code&gt;ResultRow&lt;/code&gt;/&lt;code&gt;Column&lt;/code&gt; logic - and it also falls back to the regular expression if it runs into any SQL errors. More &lt;a href="https://github.com/simonw/datasette/issues/1421"&gt;in the issue&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/cloudrun/tailing-cloud-run-request-logs"&gt;Tailing Google Cloud Run request logs and importing them into SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/python/find-local-variables-in-exception-traceback"&gt;Find local variables in the traceback for an exception&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sphinx/sphinx-autodoc"&gt;Adding Sphinx autodoc to a project, and configuring Read The Docs to build it&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-x-forwarded-host"&gt;datasette-x-forwarded-host&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-x-forwarded-host/releases/tag/0.1"&gt;0.1&lt;/a&gt; - 2021-08-12
&lt;br /&gt;Treat the X-Forwarded-Host header as the Host header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/sqlite-utils/releases/tag/3.15.1"&gt;3.15.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/sqlite-utils/releases"&gt;84 releases total&lt;/a&gt;) - 2021-08-10
&lt;br /&gt;Python CLI utility and library for manipulating SQLite databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-query-links"&gt;datasette-query-links&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-query-links/releases/tag/0.1.2"&gt;0.1.2&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-query-links/releases"&gt;3 releases total&lt;/a&gt;) - 2021-08-09
&lt;br /&gt;Turn SELECT queries returned by a query into links to execute them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.59a1"&gt;0.59a1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette/releases"&gt;96 releases total&lt;/a&gt;) - 2021-08-09
&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-pyinstrument"&gt;datasette-pyinstrument&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-pyinstrument/releases/tag/0.1"&gt;0.1&lt;/a&gt; - 2021-08-08
&lt;br /&gt;Use pyinstrument to analyze Datasette page performance&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/documentation"&gt;documentation&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="documentation"/><category term="github"/><category term="sql"/><category term="sqlite"/><category term="datasette"/><category term="weeknotes"/><category term="sqlite-utils"/><category term="mypy"/><category term="github-codespaces"/></entry><entry><title>Datasette unit tests: monkeytype_call_traces</title><link href="https://simonwillison.net/2018/Aug/2/datasette-monkeytype/#atom-tag" rel="alternate"/><published>2018-08-02T21:03:27+00:00</published><updated>2018-08-02T21:03:27+00:00</updated><id>https://simonwillison.net/2018/Aug/2/datasette-monkeytype/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette-monkeytype.now.sh/monkeytype-436d0dc/monkeytype_call_traces?_facet=module&amp;amp;_facet=qualname"&gt;Datasette unit tests: monkeytype_call_traces&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Faceted browse against every function call that occurs during the execution of Datasette’s test suite. I used Instagram’s MonkeyType tool to generate this, which can run Python code and generates a SQLite database of all of the traced calls. It’s intended to be used to automatically add mypy annotations to your code, but since it produces a SQLite database as a by-product I’ve started exploring the intermediary format using Datasette. Generating this was as easy as running “monkeytype run `which pytest`” in the Datasette root directory.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1025081051360583680"&gt;@simonw&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/static-typing"&gt;static-typing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/testing"&gt;testing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="sqlite"/><category term="static-typing"/><category term="testing"/><category term="datasette"/><category term="mypy"/></entry><entry><title>Pyre: Fast Type Checking for Python</title><link href="https://simonwillison.net/2018/May/11/pyre/#atom-tag" rel="alternate"/><published>2018-05-11T17:47:26+00:00</published><updated>2018-05-11T17:47:26+00:00</updated><id>https://simonwillison.net/2018/May/11/pyre/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.facebook.com/notes/protect-the-graph/pyre-fast-type-checking-for-python/2048520695388071/"&gt;Pyre: Fast Type Checking for Python&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Facebook’s alternative to mypy. “Pyre is designed to be highly parallel, optimizing for near-instant responses so that you get immediate feedback, even in a large codebase”. Like their Hack type checker for PHP, Pyre is implemented in OCaml.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=17048446"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/facebook"&gt;facebook&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/static-typing"&gt;static-typing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ocaml"&gt;ocaml&lt;/a&gt;&lt;/p&gt;



</summary><category term="facebook"/><category term="python"/><category term="static-typing"/><category term="mypy"/><category term="ocaml"/></entry><entry><title>How to Use Static Type Checking in Python 3.6</title><link href="https://simonwillison.net/2018/Apr/19/how-use-static-type-checking-python-36/#atom-tag" rel="alternate"/><published>2018-04-19T18:30:37+00:00</published><updated>2018-04-19T18:30:37+00:00</updated><id>https://simonwillison.net/2018/Apr/19/how-use-static-type-checking-python-36/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/@ageitgey/learn-how-to-use-static-type-checking-in-python-3-6-in-10-minutes-12c86d72677b"&gt;How to Use Static Type Checking in Python 3.6&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Useful introduction to optional static typing in Python 3.6, including how to use mypy, PyCharm and the Atom mypy plugin.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://medium.freecodecamp.org/python-collection-of-my-favorite-articles-8469b8455939"&gt;Gergely Szerovay&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/static-typing"&gt;static-typing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="static-typing"/><category term="mypy"/></entry></feed>