<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: adam-johnson</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/adam-johnson.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-04-10T16:27:27+00:00</updated><author><name>Simon Willison</name></author><entry><title>Django: what’s new in 5.2</title><link href="https://simonwillison.net/2025/Apr/10/django-whats-new-in-52/#atom-tag" rel="alternate"/><published>2025-04-10T16:27:27+00:00</published><updated>2025-04-10T16:27:27+00:00</updated><id>https://simonwillison.net/2025/Apr/10/django-whats-new-in-52/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://adamj.eu/tech/2025/04/07/django-whats-new-5.2/"&gt;Django: what’s new in 5.2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson provides extremely detailed unofficial annotated release notes for the &lt;a href="https://docs.djangoproject.com/en/5.2/releases/5.2/"&gt;latest Django&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I found his explanation and example of &lt;a href="https://adamj.eu/tech/2025/04/07/django-whats-new-5.2/#form-boundfield-customization"&gt;Form BoundField customization&lt;/a&gt; particularly useful - here's the new pattern for customizing the &lt;code&gt;class=&lt;/code&gt; attribute on the label associated with a &lt;code&gt;CharField&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;django&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;forms&lt;/span&gt;

&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;WideLabelBoundField&lt;/span&gt;(&lt;span class="pl-s1"&gt;forms&lt;/span&gt;.&lt;span class="pl-c1"&gt;BoundField&lt;/span&gt;):
    &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;label_tag&lt;/span&gt;(&lt;span class="pl-s1"&gt;self&lt;/span&gt;, &lt;span class="pl-s1"&gt;contents&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;None&lt;/span&gt;, &lt;span class="pl-s1"&gt;attrs&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;None&lt;/span&gt;, &lt;span class="pl-s1"&gt;label_suffix&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;None&lt;/span&gt;):
        &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;attrs&lt;/span&gt; &lt;span class="pl-c1"&gt;is&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;:
            &lt;span class="pl-s1"&gt;attrs&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; {}
        &lt;span class="pl-s1"&gt;attrs&lt;/span&gt;[&lt;span class="pl-s"&gt;"class"&lt;/span&gt;] &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"wide"&lt;/span&gt;
        &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-en"&gt;super&lt;/span&gt;().&lt;span class="pl-c1"&gt;label_tag&lt;/span&gt;(&lt;span class="pl-s1"&gt;contents&lt;/span&gt;, &lt;span class="pl-s1"&gt;attrs&lt;/span&gt;, &lt;span class="pl-s1"&gt;label_suffix&lt;/span&gt;)

&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;NebulaForm&lt;/span&gt;(&lt;span class="pl-s1"&gt;forms&lt;/span&gt;.&lt;span class="pl-c1"&gt;Form&lt;/span&gt;):
    &lt;span class="pl-s1"&gt;name&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;forms&lt;/span&gt;.&lt;span class="pl-c1"&gt;CharField&lt;/span&gt;(
        &lt;span class="pl-s1"&gt;max_length&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;100&lt;/span&gt;,
        &lt;span class="pl-s1"&gt;label&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Nebula Name"&lt;/span&gt;,
        &lt;span class="pl-s1"&gt;bound_field_class&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-v"&gt;WideLabelBoundField&lt;/span&gt;,
    )&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'd also missed the new &lt;a href="https://adamj.eu/tech/2025/04/07/django-whats-new-5.2/#httpresponse-get-preferred-type"&gt;HttpResponse.get_preferred_type() method&lt;/a&gt; for implementing HTTP content negotiation:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;content_type&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;request&lt;/span&gt;.&lt;span class="pl-c1"&gt;get_preferred_type&lt;/span&gt;(
    [&lt;span class="pl-s"&gt;"text/html"&lt;/span&gt;, &lt;span class="pl-s"&gt;"application/json"&lt;/span&gt;]
)&lt;/pre&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="python"/><category term="adam-johnson"/></entry><entry><title>cibuildwheel 2.20.0 now builds Python 3.13 wheels by default</title><link href="https://simonwillison.net/2024/Aug/6/cibuildwheel/#atom-tag" rel="alternate"/><published>2024-08-06T22:54:44+00:00</published><updated>2024-08-06T22:54:44+00:00</updated><id>https://simonwillison.net/2024/Aug/6/cibuildwheel/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pypa/cibuildwheel/releases/tag/v2.20.0"&gt;cibuildwheel 2.20.0 now builds Python 3.13 wheels by default&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CPython 3.13 wheels are now built by default […] This release includes CPython 3.13.0rc1, which is guaranteed to be ABI compatible with the final release.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://cibuildwheel.pypa.io/"&gt;cibuildwheel&lt;/a&gt; is an underrated but crucial piece of the overall Python ecosystem.&lt;/p&gt;
&lt;p&gt;Python wheel packages that include binary compiled components - packages with C extensions for example - need to be built multiple times, once for each combination of Python version, operating system and architecture.&lt;/p&gt;
&lt;p&gt;A package like Adam Johnson’s &lt;a href="https://github.com/adamchainz/time-machine"&gt;time-machine&lt;/a&gt; - which bundles a &lt;a href="https://github.com/adamchainz/time-machine/blob/main/src/_time_machine.c"&gt;500 line C extension&lt;/a&gt; - can end up with &lt;a href="https://pypi.org/project/time-machine/#files"&gt;55 different wheel files&lt;/a&gt; with names like &lt;code&gt;time_machine-2.15.0-cp313-cp313-win_arm64.whl&lt;/code&gt; and &lt;code&gt;time_machine-2.15.0-cp38-cp38-musllinux_1_2_x86_64.whl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Without these wheels, anyone who runs &lt;code&gt;pip install time-machine&lt;/code&gt; will need to have a working C compiler toolchain on their machine for the command to work.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cibuildwheel&lt;/code&gt; solves the problem of building all of those wheels for all of those different platforms on the CI provider of your choice. Adam is using it in GitHub Actions for &lt;code&gt;time-machine&lt;/code&gt;, and his &lt;a href="https://github.com/adamchainz/time-machine/blob/2.15.0/.github/workflows/build.yml"&gt;.github/workflows/build.yml&lt;/a&gt; file neatly demonstrates how concise the configuration can be once you figure out how to use it.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.python.org/downloads/release/python-3130rc1/"&gt;first release candidate of Python 3.13&lt;/a&gt; hit its target release date of August 1st, and the final version looks on schedule for release on the 1st of October. Since this rc should be binary compatible with the final build now is the time to start shipping those wheels to PyPI.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/continuous-integration"&gt;continuous-integration&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &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/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="continuous-integration"/><category term="packaging"/><category term="pypi"/><category term="python"/><category term="adam-johnson"/></entry><entry><title>GitHub Actions: Faster Python runs with cached virtual environments</title><link href="https://simonwillison.net/2024/Jul/19/github-actions-faster-python/#atom-tag" rel="alternate"/><published>2024-07-19T14:14:52+00:00</published><updated>2024-07-19T14:14:52+00:00</updated><id>https://simonwillison.net/2024/Jul/19/github-actions-faster-python/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://adamj.eu/tech/2023/11/02/github-actions-faster-python-virtual-environments/"&gt;GitHub Actions: Faster Python runs with cached virtual environments&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson shares his improved pattern for caching Python environments in GitHub Actions.&lt;/p&gt;
&lt;p&gt;I've been using the pattern where you add &lt;code&gt;cache: pip&lt;/code&gt; to the &lt;code&gt;actions/setup-python&lt;/code&gt; block, but it has two disadvantages: if the tests fail the cache won't be saved at the end, and it still spends time installing the packages despite not needing to download them fresh since the wheels are in the cache.&lt;/p&gt;
&lt;p&gt;Adam's pattern works differently: he caches the entire &lt;code&gt;.venv/&lt;/code&gt; folder between runs, avoiding the overhead of installing all of those packages. He also wraps the block that installs the packages between explicit &lt;code&gt;actions/cache/restore&lt;/code&gt; and &lt;code&gt;actions/cache/save&lt;/code&gt; steps to avoid the case where failed tests skip the cache persistence.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://fosstodon.org/@adamchainz/112812487815431872"&gt;@adamchainz&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/github-actions"&gt;github-actions&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="github-actions"/><category term="adam-johnson"/></entry><entry><title>Django: Test for pending migrations</title><link href="https://simonwillison.net/2024/Jun/28/django-test-for-pending-migrations/#atom-tag" rel="alternate"/><published>2024-06-28T15:23:00+00:00</published><updated>2024-06-28T15:23:00+00:00</updated><id>https://simonwillison.net/2024/Jun/28/django-test-for-pending-migrations/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://adamj.eu/tech/2024/06/23/django-test-pending-migrations/"&gt;Django: Test for pending migrations&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat recipe from Adam Johnson for adding an automated test to your Django test suite that runs &lt;code&gt;manage.py makemigrations --check&lt;/code&gt; to ensure you don't accidentally land code that deploys with a missing migration and crashes your site. I've made this mistake before myself so I'll be adding this to my projects.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://fosstodon.org/@adamchainz/112687118729636820"&gt;@adamchainz&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="adam-johnson"/></entry><entry><title>pkgutil.resolve_name(name)</title><link href="https://simonwillison.net/2024/Jun/17/pkgutil-resolve-name/#atom-tag" rel="alternate"/><published>2024-06-17T20:32:29+00:00</published><updated>2024-06-17T20:32:29+00:00</updated><id>https://simonwillison.net/2024/Jun/17/pkgutil-resolve-name/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.python.org/3/library/pkgutil.html#pkgutil.resolve_name"&gt;pkgutil.resolve_name(name)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson pointed out this utility method, added to the Python standard library in Python 3.9. It lets you provide a string that specifies a Python identifier to import from a module - a pattern frequently used in things like Django's configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Path = pkgutil.resolve_name("pathlib:Path")
&lt;/code&gt;&lt;/pre&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://adamj.eu/tech/2024/06/17/python-import-by-string/"&gt;Python: Import by string with pkgutil.resolve_name()&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/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="adam-johnson"/></entry><entry><title>time-machine example test for a segfault in Python</title><link href="https://simonwillison.net/2024/Mar/23/test-segfault-in-python/#atom-tag" rel="alternate"/><published>2024-03-23T19:44:07+00:00</published><updated>2024-03-23T19:44:07+00:00</updated><id>https://simonwillison.net/2024/Mar/23/test-segfault-in-python/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/adamchainz/time-machine/pull/433/files#diff-92ea7165ddf0128246b9758ee9554b3eccb4eceb3d4719bdea9f5495ebbe10a1R477-R495"&gt;time-machine example test for a segfault in Python&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Here's a really neat testing trick by Adam Johnson. Someone reported a segfault bug in his time-machine library. How you you write a unit test that exercises a segfault without crashing the entire test suite?&lt;/p&gt;
&lt;p&gt;Adam's solution is a test that does this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;subprocess.run([sys.executable, "-c", code_that_crashes_python], check=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sys.executable&lt;/code&gt; is the path to the current Python executable - ensuring the code will run in the same virtual environment as the test suite itself. The &lt;code&gt;-c&lt;/code&gt; option can be used to have it run a (multi-line) string of Python code, and &lt;code&gt;check=True&lt;/code&gt; causes the &lt;code&gt;subprocess.run()&lt;/code&gt; function to raise an error if the subprocess fails to execute cleanly and returns an error code.&lt;/p&gt;
&lt;p&gt;I'm absolutely going to be borrowing this pattern next time I need to add tests to cover a crashing bug in one of my projects.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://fosstodon.org/@adamchainz/112144774490159195"&gt;@adamchainz&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/testing"&gt;testing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="testing"/><category term="adam-johnson"/></entry><entry><title>How to implement a “dry run mode” for data imports in Django</title><link href="https://simonwillison.net/2022/Oct/13/dry-run-mode/#atom-tag" rel="alternate"/><published>2022-10-13T16:22:09+00:00</published><updated>2022-10-13T16:22:09+00:00</updated><id>https://simonwillison.net/2022/Oct/13/dry-run-mode/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://adamj.eu/tech/2022/10/13/dry-run-mode-for-data-imports-in-django/"&gt;How to implement a “dry run mode” for data imports in Django&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson describes in detail a beautiful pattern for implementing a dry-run mode for a Django management command, by executing ORM calls inside an &lt;code&gt;atomic()&lt;/code&gt; transaction block, showing a summary of changes that are made and then rolling the transaction back at the end.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/transactions"&gt;transactions&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="transactions"/><category term="adam-johnson"/></entry><entry><title>How to Add a Favicon to Your Django Site</title><link href="https://simonwillison.net/2022/Jan/20/how-to-add-a-favicon-to-your-django-site/#atom-tag" rel="alternate"/><published>2022-01-20T07:03:43+00:00</published><updated>2022-01-20T07:03:43+00:00</updated><id>https://simonwillison.net/2022/Jan/20/how-to-add-a-favicon-to-your-django-site/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://adamj.eu/tech/2022/01/18/how-to-add-a-favicon-to-your-django-site/"&gt;How to Add a Favicon to Your Django Site&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson did the research on the best way to handle favicons - Safari still doesn't handle SVG icons so the best solution today is a PNG served from the &lt;code&gt;/favicon.ico&lt;/code&gt; path. This article inspired me to finally add a proper favicon to Datasette.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/favicons"&gt;favicons&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="favicons"/><category term="adam-johnson"/></entry><entry><title>django-upgrade</title><link href="https://simonwillison.net/2021/Sep/26/django-upgrade/#atom-tag" rel="alternate"/><published>2021-09-26T05:42:54+00:00</published><updated>2021-09-26T05:42:54+00:00</updated><id>https://simonwillison.net/2021/Sep/26/django-upgrade/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/adamchainz/django-upgrade"&gt;django-upgrade&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson’s new CLI tool for upgrading Django projects by automatically applying changes to counter deprecations made in different versions of the framework. Uses the Python standard library tokenize module which gives it really quick performance in parsing and rewriting Python code. Exciting to see this kind of codemod approach becoming more common in Python world—JavaScript developers use this kind of thing a lot.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://adamj.eu/tech/2021/09/16/introducing-django-upgrade/"&gt;Adam Johnson&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="django"/><category term="python"/><category term="adam-johnson"/></entry><entry><title>Better Python Decorators with wrapt</title><link href="https://simonwillison.net/2020/Jul/2/better-python-decorators-wrapt/#atom-tag" rel="alternate"/><published>2020-07-02T21:48:39+00:00</published><updated>2020-07-02T21:48:39+00:00</updated><id>https://simonwillison.net/2020/Jul/2/better-python-decorators-wrapt/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://adamj.eu/tech/2020/07/02/better-python-decorators-with-wrapt/"&gt;Better Python Decorators with wrapt&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Adam Johnson explains the intricacies of decorating a Python function without breaking the ability to correctly introspect it, and discusses how Scout use the &lt;a href="https://pypi.org/project/wrapt/"&gt;wrapt&lt;/a&gt; library by Graham Dumpleton to implement their instrumentation library.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/decorators"&gt;decorators&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/adam-johnson"&gt;adam-johnson&lt;/a&gt;&lt;/p&gt;



</summary><category term="decorators"/><category term="python"/><category term="adam-johnson"/></entry></feed>