<?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: Code Review Doctor</title>
    <description>The latest articles on DEV Community by Code Review Doctor (@codereviewdoctor).</description>
    <link>https://dev.to/codereviewdoctor</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F507953%2F86989429-005e-4d57-8207-87c6cfa557e6.jpg</url>
      <title>DEV Community: Code Review Doctor</title>
      <link>https://dev.to/codereviewdoctor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codereviewdoctor"/>
    <language>en</language>
    <item>
      <title>The JSON trick 25% of Python devs don't know about</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Wed, 09 Feb 2022 01:06:13 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/the-json-trick-25-of-python-devs-dont-know-about-including-devs-at-microsoft-sentry-unicef-and-more-4h10</link>
      <guid>https://dev.to/codereviewdoctor/the-json-trick-25-of-python-devs-dont-know-about-including-devs-at-microsoft-sentry-unicef-and-more-4h10</guid>
      <description>&lt;p&gt;Reading and writing JSON files is a common chore in Python. In fact our analysis of 888 codebases found that 17% of repositories either read from or wrote to JSON files. Of those 150 codebases 25% were making their lives harder than necessary by not using a built-in JSON feature. A key part of our job as devs is to reduce the complexity of the code we write, so this is a good area of improvement for one in four Python devs.&lt;/p&gt;

&lt;p&gt;At the end of this article is a link to the stats. You may want to check the stats. You never know, your repo might be one of the 888 we checked!&lt;/p&gt;

&lt;p&gt;Many of the occurrences were in big open source projects, so this is not just a rookie mistake. For fun we listed some notable examples near the end of the article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading JSON files
&lt;/h3&gt;

&lt;p&gt;Have you ever wondered why &lt;a href="https://docs.python.org/3/library/json.html#json.loads" rel="noopener noreferrer"&gt;&lt;code&gt;json.loads&lt;/code&gt;&lt;/a&gt; ends with a "s"? The reason is because that method is specifically for working on a string, and there is another related method for working on files: &lt;a href="https://docs.python.org/3/library/json.html#json.load" rel="noopener noreferrer"&gt;&lt;code&gt;json.load&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json.loads&lt;/code&gt; is for deserialising a string containing JSON, while &lt;code&gt;json.load&lt;/code&gt; (no "s") is for deserialising a file containing JSON.&lt;/p&gt;

&lt;p&gt;The methods are very related. In fact, &lt;a href="https://github.com/python/cpython/blob/d7c567b08f9d7d6aef21b881340a2b72731129db/Lib/json/__init__.py#L293" rel="noopener noreferrer"&gt;&lt;code&gt;json.load&lt;/code&gt; is a wrapper around &lt;code&gt;json.loads&lt;/code&gt;&lt;/a&gt;. It's a method provided out of the box by Python to simplify the task of reading JSON from file-like objects. For example, these result in the same outcome:&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;# bad
&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;some/path.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;content&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;loads&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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# good
&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;some/path.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;content&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So why not choose the easiest to read and write?&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing JSON files
&lt;/h3&gt;

&lt;p&gt;Similarly to &lt;code&gt;json.loads&lt;/code&gt;, &lt;a href="https://docs.python.org/3/library/json.html#json.dumps" rel="noopener noreferrer"&gt;&lt;code&gt;json.dumps&lt;/code&gt;&lt;/a&gt; also ends with an "s" because it's specifically for working on a string and there is another related method: &lt;a href="https://docs.python.org/3/library/json.html#json.dump" rel="noopener noreferrer"&gt;&lt;code&gt;json.dump&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json.dumps&lt;/code&gt; is for JSON serialising an object to a string, while &lt;code&gt;json.dump&lt;/code&gt; (no "s") is for JSON serialising an object to a file.&lt;/p&gt;

&lt;p&gt;Again similar to the relationship between &lt;code&gt;json.load&lt;/code&gt; and &lt;code&gt;json.loads&lt;/code&gt;, &lt;a href="https://github.com/python/cpython/blob/d7c567b08f9d7d6aef21b881340a2b72731129db/Lib/json/__init__.py#L120" rel="noopener noreferrer"&gt;&lt;code&gt;json.dump&lt;/code&gt; is a wrapper around &lt;code&gt;json.dumps&lt;/code&gt;&lt;/a&gt;. It's a method provided out of the box by Python to simplify the task of writing JSON to a file-like object.&lt;/p&gt;

&lt;p&gt;For example, these result in the same outcome:&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;# bad
&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;some/path.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foo&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;bar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="c1"&gt;# good
&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;some/path.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foo&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;bar&lt;/span&gt;&lt;span class="sh"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So why not choose the easiest to read and write?&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduce complexity
&lt;/h3&gt;

&lt;p&gt;Python core developers are very careful about what features are included in the Python built-in modules because once they expose an API to devs it's very hard for them to remove it. So we've been given some tools that simplify our handling of JSON files so it makes sense to utilise the convenience methods and not reinvent the wheel. For that reason when &lt;a href="https://codereview.doctor" rel="noopener noreferrer"&gt;Code Review Doctor&lt;/a&gt; sees devs not using &lt;code&gt;json.load&lt;/code&gt; and &lt;code&gt;json.dump&lt;/code&gt; this advice is given:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiayuhyqmr67j53sigx2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiayuhyqmr67j53sigx2u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4osa534var1q4eqaqz6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4osa534var1q4eqaqz6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;p&gt;Many of the occurrences were in big open source projects, so this is not just a mistake rookies make, but rather is a often overlooked feature of Python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/dpu-utils/blob/72cac67490ccf19fede6b86269c57843549b526c/python/tests/utils/test_richpath.py#L71" rel="noopener noreferrer"&gt;Microsoft&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ansible/ansible/blob/5bddecb048aae0a7fc84e38e086ac90445c4edb4/test/units/galaxy/test_api.py#L1062" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/unicef/etools/blob/5b5fb14806ebc6696f03e15f6a0b0fd23746e105/src/etools/applications/partners/tests/test_api_prp.py#L58" rel="noopener noreferrer"&gt;Unicef&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/getsentry/sentry/blob/bdae0c34b042467f15dd73d9da5fb8749dc59a58/src/sentry/utils/distutils/commands/build_js_sdk_registry.py#L29" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/uktrade/directory-tests/blob/5aabf7bb0e923fd9afccdc99a0f1be90e7cce111/update_results.py#L22" rel="noopener noreferrer"&gt;UK Department for International Trade&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stats
&lt;/h3&gt;

&lt;p&gt;You can read the gists: &lt;br&gt;
Reading JSON: &lt;a href="https://gist.github.com/code-review-doctor/f6cd072becd256fe7c81b24ab3db58d3" rel="noopener noreferrer"&gt;json.load vs json.loads&lt;/a&gt;&lt;br&gt;
Writing JSON: &lt;a href="https://gist.github.com/code-review-doctor/b457f8e9020124cdd294f0bdf443deb9" rel="noopener noreferrer"&gt;json.dump vs json.dumps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How did we find this statistic? At &lt;a href="https://codereview.doctor" rel="noopener noreferrer"&gt;CodeReview.doctor&lt;/a&gt; we run static analysis checks on codebases to find bugs and mistakes most humans miss. During this process we collect stats on the public repos. We tracked every line we could find where developers interacted with JSON files. We were surprised by the results!&lt;/p&gt;

&lt;p&gt;If you would like to check if you're affected by issues like this one, check your codebase instantly for free at &lt;a href="https://codereview.doctor" rel="noopener noreferrer"&gt;CodeReview.doctor&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>11% of Python files are not closed! Affecting Google, Python.org, Ansible, Tensorflow ...</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Mon, 31 Jan 2022 20:30:51 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/11-of-python-files-are-not-closed-affecting-google-pythonorg-ansible-tensorflow--1p97</link>
      <guid>https://dev.to/codereviewdoctor/11-of-python-files-are-not-closed-affecting-google-pythonorg-ansible-tensorflow--1p97</guid>
      <description>&lt;p&gt;It’s a problem that 11% of files are not closed properly as leaving files open degrades application performance and increases the risk of unexpected side effects.&lt;/p&gt;

&lt;p&gt;It's not just a mistake that rookies make. Many of the offenders are big names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/python/pythondotorg/blob/4ad9d4083290cb063d074a02e41cf9ef6e0e0c73/pages/parser.py#L18" rel="noopener noreferrer"&gt;python.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/googleapis/google-api-python-client/blob/4e0aa62ae80e56036d9a3435d15b7263575b3609/samples/compute/create_instance.py#L48" rel="noopener noreferrer"&gt;Google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ansible/ansible/blob/bc5672d9ba2d30f8f96e135079f412659350c574/lib/ansible/modules/service.py#L1381" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tensorflow/tensorflow/blob/ad77ca7d07ee05d9f656edcf082d39d4bbd78114/tensorflow/lite/experimental/acceleration/mini_benchmark/metrics/blazeface_metrics.py#L115" rel="noopener noreferrer"&gt;Tensorflow&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are just 4 of the &amp;gt;500 examples we found in the 5409 &lt;code&gt;open(…)&lt;/code&gt; calls we analyzed over 666 repositories.&lt;/p&gt;

&lt;p&gt;How did we find this statistic? At &lt;a href="//CodeReview.doctor"&gt;CodeReview.doctor&lt;/a&gt; we run static analysis checks on codebases, and we collect stats on the public repos. We tracked every line we could find where developers opened files via &lt;code&gt;open(...)&lt;/code&gt; but never closed them and compared that with every line we could find where developers "did it right” (either explicitly calling &lt;code&gt;close()&lt;/code&gt; or implicitly via context manager usage).&lt;/p&gt;

&lt;p&gt;At the end of this article is a link and embed of the stats.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;To recap: leaving files open results in degradation of application performance and increases the risk of unexpected side effects.&lt;/p&gt;

&lt;p&gt;Computers have finite resources, including limits on how many files can be simultaneously open. Therefore leaving files open consumes more finite file handles than necessary. Under extreme conditions this could exhaust the number of allowed open files causing an “IOError: Too many open files" exception to be raised. Additionally, leaving files open consumes finite memory for longer than is necessary. Therefore for performance of the application it is preferable to closing file after finished with them.&lt;/p&gt;

&lt;p&gt;Python’s &lt;a href="https://stackify.com/python-garbage-collection/" rel="noopener noreferrer"&gt;garbage collection&lt;/a&gt; will eventually close dangling unclosed files, but "eventually" should raise a red flag if the goal is to write predictable software. Indeed, Python3 and Cpython might implicitly close the file the instant they’re no longer being used, while others might not for a long time. Under some conditions it might not happen at all. When pairing this problem with the zen of Python's &lt;em&gt;Explicit is better than implicit&lt;/em&gt;, it’s clear that it’s better to not leave files open.&lt;/p&gt;

&lt;p&gt;Python can also be fuzzy around exactly when file changes are committed to disk: the data may not be written to disk until the file is closed:&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;# bad: maybe not written to disk yet as the file was not closed
&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;foo.txt&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;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# also bad: maybe not written to disk as the file was not closed
&lt;/span&gt;&lt;span class="n"&gt;f&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;bar.txt&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;w&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# better: written to disk when the file closed
&lt;/span&gt;&lt;span class="n"&gt;f&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;baz.txt&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;w&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;baz&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# good: written to disk when the file closed and code is neater
&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;qux.txt&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;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;qux&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;Windows (the Operating System) locks files opened by Python. This could mean leaving files open is not thread safe as one thread will lock the file preventing other threads from opening it — even if the other thread just wants to read from it.&lt;br&gt;
That’s why we give this advice:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxk6jdkhi9k40l05wmeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxk6jdkhi9k40l05wmeu.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The stats
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/code-review-doctor/6d2ff4ab5cd29c387c14596a7ee7d92a" rel="noopener noreferrer"&gt;Here’s&lt;/a&gt; a link to the 4812 examples of "doing it right": all the files were closed either by explicitly calling &lt;code&gt;close()&lt;/code&gt; or by opening the file using the context manager approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/code-review-doctor/526e267138b79e16069def68d2bb5422" rel="noopener noreferrer"&gt;Here's&lt;/a&gt; a link to the 597 examples of not “doing it right”: the files were opened and read from but never closed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check your repo
&lt;/h3&gt;

&lt;p&gt;Check your repo for tech debt like this at &lt;a href="//codereview.doctor"&gt;codereview.doctor&lt;/a&gt;, or even &lt;a href="https://github.com/marketplace/django-doctor/" rel="noopener noreferrer"&gt;review your GitHub PRs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>codequality</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5% of 666 Python repos had comma typo bugs (including V8, Sentry, Tensorflow, and PyTorch)</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Thu, 06 Jan 2022 19:22:25 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/5-of-666-python-repos-had-comma-typo-bugs-including-v8-sentry-tensorflow-and-pytorch-10eo</link>
      <guid>https://dev.to/codereviewdoctor/5-of-666-python-repos-had-comma-typo-bugs-including-v8-sentry-tensorflow-and-pytorch-10eo</guid>
      <description>&lt;p&gt;We found that 5% of the 666 Python open source GitHub repositories had the following three comma-related bugs caused by typos:&lt;/p&gt;

&lt;h3&gt;
  
  
  Too many commas
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://codereview.doctor/features/python/best-practice/tuple-accidental-trailing-comma" rel="noopener noreferrer"&gt;Typo accidentally adding a comma to end of a value&lt;/a&gt;, turning it into a tuple&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut2zvvpk8g8dkc7dyj5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut2zvvpk8g8dkc7dyj5p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As far as the Python parser is concerned the parentheses are optional for non-empty tuples. According to the documentation: it is actually the comma which makes a tuple, not the parentheses. The parentheses are optional, except in the empty tuple case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# evaluates to int
&lt;/span&gt;&lt;span class="n"&gt;value&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="c1"&gt;# evaluates to tuple with one element
&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;  &lt;span class="c1"&gt;# evaluates to tuple with one element
&lt;/span&gt;&lt;span class="n"&gt;value&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="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# evaluates to tuple with two elements
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By accidentally suffixing a comma on value = 1, expect to get a TypeError when the code later attempts to perform integer operations on a variable that evaluated to tuple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too few commas
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://codereview.doctor/features/python/best-practice/avoid-missing-comma" rel="noopener noreferrer"&gt;Accidentally missed comma from string in list/tuple/set&lt;/a&gt;, resulting in unwanted string concatenation.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthh0a0rvl8untuuoncvo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthh0a0rvl8untuuoncvo.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implicit string concatenation that resulted from a typo can change the behaviour of the application. Take for example:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_positive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;words&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;yes&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;correct&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;affirmative&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agreed&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;

&lt;span class="nf"&gt;is_positive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agreed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# evaluates to False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;is_positive('agreed')&lt;/code&gt; evaluates to &lt;code&gt;False&lt;/code&gt; because a typo resulted in the comma being missed from the end of 'affirmative', resulting in 'affirmative' and 'agreed' being implicitly concatenated to 'affirmativeagreed'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codereview.doctor/features/python/best-practice/tuple-missing-trailing-comma" rel="noopener noreferrer"&gt;Accidentally missed comma from 1 element tuple&lt;/a&gt;, making it a &lt;code&gt;str&lt;/code&gt; instead of a &lt;code&gt;tuple&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyei5e15k83cc1dlzxq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyei5e15k83cc1dlzxq5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As far as the Python parser is concerned the parentheses are optional for non-empty tuples. According to the documentation: it is actually the comma which makes a tuple, not the parentheses. The parentheses are optional, except in the empty tuple case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting the bugs
&lt;/h3&gt;

&lt;p&gt;We did not go through the repositories line by line. Instead we ran the repositories through our &lt;a href="http://codereview.doctor/" rel="noopener noreferrer"&gt;suite of static analysis checks&lt;/a&gt; which we run on AWS Lambda, taking a grand total of 90 seconds.&lt;/p&gt;

&lt;p&gt;The main difficulty was reducing false positives. Syntactically there is no difference between a missing comma in a list and implicit string concatenation done on purpose. Indeed, when we first wrote the "probably missing a comma" checker we found that 95% of the problems were actually false positives: there are perfectly cromulent reasons a developer would do implicit string concatenation spanning multiple lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User agent string &lt;/li&gt;
&lt;li&gt;file path&lt;/li&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;li&gt;CSV file contents&lt;/li&gt;
&lt;li&gt;Very long message&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;Sha hash&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After checking the 666 codebases we understood the key drivers of false positives and added allowances for implicit string concatenation for those types of data. After we gave allowances for these valid reasons to do implicit string concatenation the false positive rate went down to negligible non-annoying level. At this point we were left with a list of 24 repositories that had real bugs that slipped through code review process.&lt;/p&gt;

&lt;h2&gt;
  
  
  How common?
&lt;/h2&gt;

&lt;p&gt;Overall we detected these bugs in 24 of the 666 repositories - and many of them were big open source projects. We raised the issues and helped fix over the course of a very busy day of managing tickets and PRs. Here's the most interesting ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/getsentry/sentry/issues/30911" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tensorflow/tensorflow/issues/53636" rel="noopener noreferrer"&gt;Tensorflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pydata/xarray/issues/6136" rel="noopener noreferrer"&gt;Pydata's xarray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bugs.chromium.org/p/v8/issues/detail?id=12521" rel="noopener noreferrer"&gt;Google's V8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/70609" rel="noopener noreferrer"&gt;PyTorch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/70607" rel="noopener noreferrer"&gt;PyTorch again&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/70611" rel="noopener noreferrer"&gt;Pytorch the third&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rapidpro/rapidpro/issues/1599" rel="noopener noreferrer"&gt;rapidpro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabiocaccamo/django-colorfield/issues/76" rel="noopener noreferrer"&gt;django-colorfield&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/django-helpdesk/django-helpdesk/issues/988" rel="noopener noreferrer"&gt;django-helpdesk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5% of repositories had one of these three bugs, and most of them were "big" well known and well-used projects with many contributors and very robust code review processes. This highlights that for manual processes like code review to detect 100% of bugs 100% of the time then 100% of humans must perform 100% perfectly during code review 100% of the time. See the cognitive dissonance? We do code review because we expect human error when writing the code, but then expect no human error when reviewing the code. There are just some things a human can do well, and there are some things a bot can do better. Noticing commas in the wrong place is one of those things. For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q5ex7jehj0wpoq25hou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q5ex7jehj0wpoq25hou.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see the problem? &lt;a href="https://github.com/rapidpro/rapidpro/issues/1599" rel="noopener noreferrer"&gt;Line 572 has implicit string concatenation&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;It's missing a commas, so should be &lt;br&gt;
&lt;code&gt;'"()&amp;lt;&amp;gt;[]:,;@\\"!#$%&amp;amp;\'-/=?^_{}| ~.a"@example.org', '" "@example.org',&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I'm not surprised that was missed by a human!&lt;/p&gt;

&lt;h2&gt;
  
  
  The impact
&lt;/h2&gt;

&lt;p&gt;Some of the missing commas have little to no impact, but some are rather impactful. Take the &lt;a href="https://github.com/getsentry/sentry/issues/30911" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; one as an example:&lt;/p&gt;

&lt;p&gt;Notice the missing comma in the list:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wy5l1wq10qjk5gblu78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wy5l1wq10qjk5gblu78.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a comma missing between "releases" and "discover" resulting in the two being implicitly concatenated together to form "releasesdiscover".&lt;/p&gt;

&lt;p&gt;The impact is the test requesting "/releasesdiscover" which does not exist, and so instead of "/releases" and "/discover" being tested, instead the 404 page is tested. That means for all we know, the organisation switcher on /releases and /discover could be broken. There is some poor dev out there maintaining the organisation switcher relying on this test and using it as a quality gate to give them confidence they have not broken the product but the test is not really working. It's these kind of things that can harm a nights sleep! &lt;em&gt;It ain't what you don't know that gets you into trouble. It's what you know for sure that just ain't so.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Protecting your codebase
&lt;/h3&gt;

&lt;p&gt;You can add &lt;a href="https://github.com/marketplace/django-doctor/" rel="noopener noreferrer"&gt;Code Review Doctor&lt;/a&gt; to GitHub so fixes for problems like these are suggested right inside your PR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flq3x1pgbbop2zqptmww4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flq3x1pgbbop2zqptmww4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>How we found and helped fix 24 bugs in 24 hours (in Tensorflow, Sentry, V8, PyTorch, Hue, and more)</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Wed, 05 Jan 2022 10:36:33 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/how-we-found-and-helped-fix-24bugs-in-24-hours-in-tensorflow-sentry-v8-pytorch-hue-and-more-1i5m</link>
      <guid>https://dev.to/codereviewdoctor/how-we-found-and-helped-fix-24bugs-in-24-hours-in-tensorflow-sentry-v8-pytorch-hue-and-more-1i5m</guid>
      <description>&lt;p&gt;Code Review Doctor is a static analysis tool &lt;a href="https://github.com/marketplace/django-doctor/"&gt;integrated into GitHub&lt;/a&gt;. Normally it suggests fixes in PRs - but during integration testing of new checks we run the checks against 666 open source GitHub repositories to ensure the quality of the checks (including weeding out false positives before our users see them).&lt;/p&gt;

&lt;p&gt;During the integration testing this week we found 24 bugs in big open source projects, which we raised and helped fix over the course of a very busy day of managing tickets and PRs. Here's the most interesting ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/getsentry/sentry/issues/30911"&gt;Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tensorflow/tensorflow/issues/53636"&gt;Tensorflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pydata/xarray/issues/6136"&gt;Pydata's xarray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bugs.chromium.org/p/v8/issues/detail?id=12521"&gt;Google's V8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/70609"&gt;PyTorch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/70607"&gt;PyTorch again&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/70611"&gt;Pytorch the third&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rapidpro/rapidpro/issues/1599"&gt;rapidpro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabiocaccamo/django-colorfield/issues/76"&gt;django-colorfield&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/django-helpdesk/django-helpdesk/issues/988"&gt;django-helpdesk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The checks
&lt;/h1&gt;

&lt;p&gt;A common thread is all the issues relate to commas: either too many commas or too few commas:&lt;/p&gt;

&lt;h3&gt;
  
  
  Too many commas
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://codereview.doctor/features/python/best-practice/tuple-accidental-trailing-comma"&gt;Typo accidentally adding a comma to end of a value&lt;/a&gt;, turning it into a tuple&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Plnt3ByI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ut2zvvpk8g8dkc7dyj5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Plnt3ByI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ut2zvvpk8g8dkc7dyj5p.png" alt="Image description" width="487" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Too few commas
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://codereview.doctor/features/python/best-practice/avoid-missing-comma"&gt;Accidentally missed comma from string in list/tuple/set&lt;/a&gt;, resulting in unwanted string concatenation.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lZBXbDdP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thh0a0rvl8untuuoncvo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lZBXbDdP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thh0a0rvl8untuuoncvo.png" alt="Image description" width="880" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codereview.doctor/features/python/best-practice/tuple-missing-trailing-comma"&gt;Accidentally missed comma from 1 element tuple&lt;/a&gt;, making it a &lt;code&gt;str&lt;/code&gt; instead of a &lt;code&gt;tuple&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wj0YsZmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyei5e15k83cc1dlzxq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wj0YsZmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyei5e15k83cc1dlzxq5.png" alt="Image description" width="758" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main difficult bit with this check was reducing false positives. Syntactically there is no difference between a missing comma in a list and implicit string concatenation done on purpose. Indeed, when we first wrote the checker we found that 95% of the "problems" were actually false positives: there are perfectly cromulent reasons a developer would do implicit string concatenation spanning multiple lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User agent string &lt;/li&gt;
&lt;li&gt;file path&lt;/li&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;li&gt;CSV file contents&lt;/li&gt;
&lt;li&gt;Very long message&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;Sha hash&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After checking the 666 codebases we understood the key drivers of false positives and added allowances for implicit string concatenation for those types of data. After we gave allowances for these valid reasons to do implicit string concatenation the false positive rate went down to negligible non-annoying level.&lt;/p&gt;

&lt;h2&gt;
  
  
  How common?
&lt;/h2&gt;

&lt;p&gt;After running those three new checkers against the 666 repositories we found that almost 5% of repositories had one of these three bugs, and most of them were "big" well known and well-used projects with many contributors and very robust code review processes. This highlights that for manual processes like code review to detect 100% of bugs 100% of the time then 100% of humans must perform 100% perfectly during code review 100% of the time. See the cognitive dissonance? We do code review because we expect human error when writing the code, but then expect no human error when reviewing the code. There are just some things a human can do well, and there are some things a bot can do better. Noticing commas in the wrong place is one of those things. For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YCegpzo2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8q5ex7jehj0wpoq25hou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YCegpzo2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8q5ex7jehj0wpoq25hou.png" alt="Image description" width="740" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you see the problem? &lt;a href="https://github.com/rapidpro/rapidpro/issues/1599"&gt;Line 572 has implicit string concatenation&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;It's missing a commas, so should be &lt;br&gt;
&lt;code&gt;'"()&amp;lt;&amp;gt;[]:,;@\\"!#$%&amp;amp;\'-/=?^_{}| ~.a"@example.org', '" "@example.org',&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I'm not surprised that was missed by a human!&lt;/p&gt;

&lt;h2&gt;
  
  
  The impact
&lt;/h2&gt;

&lt;p&gt;Some of the missing commas have little to no impact, but some are rather impactful. Take the &lt;a href="https://github.com/getsentry/sentry/issues/30911"&gt;Sentry&lt;/a&gt; one as an example:&lt;/p&gt;

&lt;p&gt;Notice the missing comma in the list:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MQtILv6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wy5l1wq10qjk5gblu78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MQtILv6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wy5l1wq10qjk5gblu78.png" alt="Image description" width="880" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a comma missing between "releases" and "discover" resulting in the two being implicitly concatenated together to form "releasesdiscover".&lt;/p&gt;

&lt;p&gt;The impact is the test requesting "/releasesdiscover" which does not exist, and so instead of "/releases" and "/discover" being tested, instead the 404 page is tested. That means for all we know, the organisation switcher on /releases and /discover could be broken. There is some poor dev out there maintaining the organisation switcher relying on this test and using it as a quality gate to give them confidence they have not broken the product but the test is not really working. It's these kind of things that can harm a nights sleep! &lt;em&gt;It ain't what you don't know that gets you into trouble. It's what you know for sure that just ain't so.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Protecting your codebase
&lt;/h3&gt;

&lt;p&gt;Add &lt;a href="https://github.com/marketplace/django-doctor/"&gt;Code Review Doctor&lt;/a&gt; to GitHub and avoid problems like these being merged as Code Review Doctor will automatically suggest the fix for &lt;a href="https://codereview.doctor"&gt;these kind of issues&lt;/a&gt; right inside your PR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lFC2aDkB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lq3x1pgbbop2zqptmww4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lFC2aDkB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lq3x1pgbbop2zqptmww4.png" alt="Image description" width="880" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>github</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Django Doctor is expanding...and rebranding!</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Wed, 15 Dec 2021 12:23:53 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/django-doctor-is-expandingand-rebranding-e4g</link>
      <guid>https://dev.to/codereviewdoctor/django-doctor-is-expandingand-rebranding-e4g</guid>
      <description>&lt;p&gt;Django Doctor is a code analysis tool that suggests improvements to your Django code right inside your pull request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhjomtr0d52j8sppgcvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhjomtr0d52j8sppgcvm.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're expanding the scope of our services to also check Python code in general. As a result we're soon renaming to Code Review Doctor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49klefdc3hinlehwkidn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49klefdc3hinlehwkidn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95mgsueo6jz0701zidlz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95mgsueo6jz0701zidlz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F132eo11p8jesxxqyg1yk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F132eo11p8jesxxqyg1yk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi94xpgq3drd9tt4gmpx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi94xpgq3drd9tt4gmpx2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5w9jtcw6rac0k59ivvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5w9jtcw6rac0k59ivvg.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sneak peak: &lt;a href="https://CodeReview.doctor" rel="noopener noreferrer"&gt;CodeReview.doctor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our prices remain unchanged, so our valued customers will get more value.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>github</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Don't abuse Django's DEBUG setting</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Tue, 12 Oct 2021 16:33:30 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/don-t-abuse-django-s-debug-setting-1flc</link>
      <guid>https://dev.to/codereviewdoctor/don-t-abuse-django-s-debug-setting-1flc</guid>
      <description>&lt;p&gt;It's tempting to treat &lt;code&gt;settings.DEBUG&lt;/code&gt; as a feature flag. Many devs see it as analogous to &lt;em&gt;is the app not running in prod&lt;/em&gt;. However, using &lt;code&gt;settings.DEBUG&lt;/code&gt; to change the flow of code adds complexities to testing and maintaining the codebase.&lt;/p&gt;

&lt;p&gt;As a concrete example, let's say we want to activate Django admin only in local development to reduce the surface area for hackers to attack in prod. We could do this:&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;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# not exposing admin to prod so it can't be hacked
&lt;/span&gt;    &lt;span class="n"&gt;urlpatterns&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="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will work, but the code will be harder to test in isolation because the value of &lt;code&gt;settings.DEBUG&lt;/code&gt; changes other Django behaviours including error handling (should we see detailed crash report in browser? Should the error be emailed to admins?). Therefore instead of using &lt;code&gt;settings.DEBUG&lt;/code&gt; in this way consider using a &lt;a href="https://en.wikipedia.org/wiki/Feature_toggle" rel="noopener noreferrer"&gt;feature flag&lt;/a&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;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IS_ADMIN_ENABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# not exposing admin to prod so it can't be hacked
&lt;/span&gt;    &lt;span class="n"&gt;urlpatterns&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="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;admin/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for this complexity is the fact &lt;code&gt;settings.DEBUG&lt;/code&gt; is for switching whether Django should run in debug mode. Indeed it's for switching behaviour of the &lt;em&gt;framework&lt;/em&gt; and &lt;em&gt;framework libraries&lt;/em&gt;, not &lt;em&gt;application code&lt;/em&gt;. Using &lt;code&gt;settings.DEBUG&lt;/code&gt; for also controlling the behaviour of the application code built on top of the framework makes the codebase harder to maintain and test. Mixing the layers and responsibilities like this adds complexity.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;DEBUG=True&lt;/code&gt; shows detailed error pages for use during local development, while &lt;code&gt;DEBUG=False&lt;/code&gt; can result in the error instead being emailed to &lt;code&gt;settings.ADMINS&lt;/code&gt;. Do you want to cause either of those behavioural changes while testing your code? Probably not, as this is inconvenient and also goes against the &lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment" rel="noopener noreferrer"&gt;principle of least surprise&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Aside from the complexities in testing, &lt;a href="https://12factor.net/" rel="noopener noreferrer"&gt;Twelve factor App&lt;/a&gt; suggests aiming for &lt;a href="https://12factor.net/dev-prod-parity" rel="noopener noreferrer"&gt;dev/prod parity&lt;/a&gt;. Using &lt;code&gt;settings.DEBUG&lt;/code&gt; to control which block of code the application runs prevents dev/prod parity because most developers will not test or run their local environment with &lt;code&gt;DEBUG=False&lt;/code&gt;. To solve this instead consider adding a feature flag specifically for this one check, which you can switch easily during unit tests and with none of the other side effects inherent with &lt;code&gt;settings.DEBUG&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we spot this issue in your GitHub pull request we give this advice:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frb0rkyw41frifamgstpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frb0rkyw41frifamgstpo.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature flag implimentation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://12factor.net" rel="noopener noreferrer"&gt;12 factor app&lt;/a&gt; suggests feature flags can be switched on or off using &lt;a href="https://12factor.net/config" rel="noopener noreferrer"&gt;environment variables&lt;/a&gt;. You could install a &lt;a href="https://django-environ.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;library&lt;/a&gt; to handle the parsing of environment variables or do something like:&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;# settings.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;iteral_eval&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;getenv&lt;/span&gt;

&lt;span class="n"&gt;IS_ADMIN_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;literal_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IS_ADMIN_ENABLED&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;False&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;&lt;a href="https://docs.python.org/3/library/ast.html#ast.literal_eval" rel="noopener noreferrer"&gt;&lt;code&gt;ast.iteral_eval&lt;/code&gt;&lt;/a&gt; is that helpful function you always needed but never knew was provided out of the box by Python. It can convert string &lt;code&gt;"False"&lt;/code&gt; to boolean &lt;code&gt;False&lt;/code&gt; and string &lt;code&gt;"True"&lt;/code&gt; to boolean &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does your Django codebase have tech debt like this?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://django.doctor" rel="noopener noreferrer"&gt;Django Doctor&lt;/a&gt; can find and fix over 40 types of common Django tech debt, and can even &lt;a href="https://github.com/marketplace/django-doctor/" rel="noopener noreferrer"&gt;review your GitHub Pull Requests&lt;/a&gt; for you.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Django feature that can slow your website to a crawl</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Sun, 10 Oct 2021 23:29:15 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/the-django-feature-that-can-slow-your-website-to-a-crawl-2f9k</link>
      <guid>https://dev.to/codereviewdoctor/the-django-feature-that-can-slow-your-website-to-a-crawl-2f9k</guid>
      <description>&lt;p&gt;Ordering Django querysets via &lt;a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#order-by" rel="noopener noreferrer"&gt;&lt;code&gt;order_by&lt;/code&gt;&lt;/a&gt; causes Django to ask the database to order the rows by a given column.&lt;/p&gt;

&lt;p&gt;As long as the number of rows being ordered is not too large then this operation should be quick. However, there is a situation where using the build-in &lt;code&gt;order_by&lt;/code&gt; can trigger a series of very expensive and slow operations: getting a random row from the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read one random row inefficiently
&lt;/h2&gt;

&lt;p&gt;Django's build in way to get a random record is via &lt;code&gt;order_by('?')[0]&lt;/code&gt;. Note though that the Django docs themselves give &lt;a href="https://docs.djangoproject.com/en/3.2/ref/models/querysets/#order-by" rel="noopener noreferrer"&gt;this warning&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;order_by('?')&lt;/code&gt; queries may be expensive and slow&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason for the poor performance is the fact Django generates the following inefficient SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;RAND&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break that SQL down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the database to include all columns from the &lt;code&gt;entry&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;RAND&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the database to generate a random number for every row (depending on database used. Some do it differently).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tell the database to scan the generated numbers for the smallest one. This rows is then read.&lt;/p&gt;

&lt;p&gt;Let that sink in: if we have 100,000 rows then 100,000 random numbers are first be generated, then all 100,000 are scanned for the smallest one. Generating random numbers is slow, and scanning is not quick. For perspective this whole operation smells a lot like mining Bitcoin but ultimately all we get out of it is we read one row from the database! A lot of work must be done for small pay-off.&lt;/p&gt;

&lt;p&gt;If there are a lot of rows in the database and &lt;code&gt;order_by('?')[0]&lt;/code&gt; is used then expect bad performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read one random row efficiently
&lt;/h2&gt;

&lt;p&gt;In short, databases are not good at random. Applications are though. So consider splitting responsibilities between the database and the application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At database level determine the number of rows&lt;/li&gt;
&lt;li&gt;At application level get a random number between 0 and that count.&lt;/li&gt;
&lt;li&gt;At database level select the row at that index.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will mean two database reads are performed, but those will are very efficient operations and so significantly quicker than the database copying the table and ordering all the rows randomly.&lt;/p&gt;

&lt;p&gt;With that in mind, that is why &lt;a href="https://django.doctor" rel="noopener noreferrer"&gt;Django Doctor&lt;/a&gt; gives this advice when reviewing Pull Requests and it spots &lt;code&gt;order_by('?')[0]&lt;/code&gt; being used:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f5t765vh19445dff1kt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f5t765vh19445dff1kt.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-contained
&lt;/h3&gt;

&lt;p&gt;Consider improving the layering of the solution by moving the logic to a custom model manager like so:&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;from&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;randint&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RandomManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@transaction.atomic&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="nf"&gt;randint&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;self&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="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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RandomManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;random_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the use of &lt;a href="https://docs.djangoproject.com/en/3.2/topics/db/transactions/" rel="noopener noreferrer"&gt;&lt;code&gt;transaction.actomic&lt;/code&gt;&lt;/a&gt; to make both the count and the row read happen in the same transaction, preventing a rare race conditions occurring if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the last row in the table was deleted after the count was read but before the random row was read.&lt;/li&gt;
&lt;li&gt;the random number generated was the the last row in the table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a atomic transaction under very rare circumstances an &lt;code&gt;IndexError&lt;/code&gt; could occur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does your Django codebase have tech debt like this?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://django.doctor" rel="noopener noreferrer"&gt;Django Doctor&lt;/a&gt; can find and fix over 40 types of common Django tech debt, and can even &lt;a href="https://github.com/marketplace/django-doctor/" rel="noopener noreferrer"&gt;review your GitHub Pull Requests&lt;/a&gt; for you.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>GUI for Python using React and distributed with pip</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Thu, 20 May 2021 20:01:06 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/gui-for-python-using-react-and-distributed-with-pip-1e1b</link>
      <guid>https://dev.to/codereviewdoctor/gui-for-python-using-react-and-distributed-with-pip-1e1b</guid>
      <description>&lt;p&gt;Django Doctor analyses codebases and suggests improvements. It used to be SaaS only, but today we released it as an offline command line tool. This blog post explains how we used React to make a nice UI for our Python command line interface:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/yC8WtCjTNSs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;For our command line tool we wanted a nice UI. We use React for our website so we considered how to pass data from Python to React and back again, in a way that can easily be distributed via &lt;code&gt;pip install django-doctor&lt;/code&gt;. We didn't use Django or Flask as we wanted an apple, not a gorilla holding an apple.&lt;/p&gt;

&lt;h1&gt;
  
  
  The code
&lt;/h1&gt;

&lt;p&gt;The following React Component is a form wizard that accepts a list of items and allows the user to choose a subset, then that subset of items is posted to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;messages&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;httpState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHttpState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;isInProgress&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="na"&gt;isComplete&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="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleSave&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedMessages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setHttpState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;isInProgress&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;isComplete&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/done/&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;POST&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&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="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedMessages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setHttpState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;isInProgress&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="na"&gt;isComplete&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setHttpState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;isInProgress&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="na"&gt;isComplete&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="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isComplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Done&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Wizard&lt;/span&gt;
      &lt;span class="na"&gt;handleSave&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSave&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;httpState&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;httpState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can pass into the component some data provided by a Python script by doing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&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;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.jsx&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="nx"&gt;contextElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;context-messages&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="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contextElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt; &lt;span class="nx"&gt;rootElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;index.js&lt;/code&gt; expects the HTML page it's being served from to contain an element with ID &lt;code&gt;context-messages&lt;/code&gt; to contain some JSON serialized data. Now is where the Python comes in. We serve the HTML file using features provided by Python's build in &lt;code&gt;wsgiref&lt;/code&gt; library:&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;# wsgi.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;mimetypes&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;wsgiref.simple_server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_server&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;wsgiref.util&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FileWrapper&lt;/span&gt;

&lt;span class="c1"&gt;# a folder containing the built React app, which we trick python into working with by adding an __init__.py to it
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;django_doctor.wizard&lt;/span&gt;


&lt;span class="n"&gt;static_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;django_doctor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wizard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__path__&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="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;static_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s"&gt;'index.html'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;home_template_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&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;home_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# exposing data to the HTML template using an approach inspired by https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#json-script
&lt;/span&gt;    &lt;span class="n"&gt;messages&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="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&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="n"&gt;home_template_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;'&amp;lt;head&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&amp;lt;head&amp;gt;&amp;lt;script id="context-messages" type="application/json"&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/script&amp;gt;'&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="n"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'200 OK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'text/html'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Content-Length'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)))])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;body&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;static_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# serve the css/js/png/etc files
&lt;/span&gt;    &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mimetypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;guess_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PATH_INFO'&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;static_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PATH_INFO'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'200 OK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&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;FileWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&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;submit_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'CONTENT_LENGTH'&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;request_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'wsgi.input'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;selected_messages&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="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: do something with selected_messages
&lt;/span&gt;    &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'200 OK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'text/plain'&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="c1"&gt;# make the server kill itself after the response is sent
&lt;/span&gt;    &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'PATH_INFO'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&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;home_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'PATH_INFO'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'/done/'&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;submit_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'PATH_INFO'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;startwith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/static/'&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;static_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respond&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;respond&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'9000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can create some command line tool that calls &lt;code&gt;wsgi.create&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="nn"&gt;argparse&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_doctor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;check_codebase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wsgi&lt;/span&gt;


&lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Django Doctor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'-d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'--directory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_codebase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wsgi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we have bi-directional communication with react and python:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A python command line script that runs &lt;code&gt;check_codebase&lt;/code&gt; then passes &lt;code&gt;messages&lt;/code&gt; to the wsgi app&lt;/li&gt;
&lt;li&gt;A wsgi app that renders a HTML file containing &lt;code&gt;messages&lt;/code&gt;, and (not shown) a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag that serves the build react js&lt;/li&gt;
&lt;li&gt;A React app that hydrates the json and then passes it to form wizard, then ultimately posts the selected items back to  &lt;code&gt;/done/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;a wsgi handler that sees data posted to /done/ and does soemthing with it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pretty cool. To make it cooler we can replace the http post request and rendering of html with a websocket. Less hacky. Maybe we will eventually use that at &lt;a href="https://django.doctor/"&gt;Django Doctor&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  distributing via pip install
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;setup.py&lt;/code&gt; is great at distributing Python files, but for this to work we need to make setup.py create a distribution containing Python files and .js and .png and .html etc.&lt;/p&gt;

&lt;p&gt;We do that by copying the build react app to &lt;code&gt;./wizard&lt;/code&gt;, add &lt;code&gt;__init__.py&lt;/code&gt; to it, then write the &lt;code&gt;setup.py&lt;/code&gt; like so:&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;setup&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="s"&gt;"django_doctor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://django.doctor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;find_packages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'django_doctor.wizard'&lt;/span&gt;&lt;span class="p"&gt;,]),&lt;/span&gt;
    &lt;span class="n"&gt;include_package_data&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;classifiers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"Development Status :: 5 - Production/Stable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Environment :: Web Environment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Framework :: Django"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Intended Audience :: Developers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Natural Language :: English"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Operating System :: OS Independent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Programming Language :: Python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Topic :: Software Development :: Libraries :: Python Modules"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meat is in &lt;code&gt;packages&lt;/code&gt; - making sure &lt;code&gt;wizard&lt;/code&gt; package is included, and &lt;code&gt;include_package_data&lt;/code&gt; to make sure the non python files are distributed too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does your Django codebase have room for improvement?
&lt;/h3&gt;

&lt;p&gt;Use our command line interface tool to check. &lt;code&gt;pip install django-doctor&lt;/code&gt; then &lt;code&gt;django_doctor fix&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Why we made a Django release notes comparison tool</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Tue, 16 Feb 2021 14:45:27 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/why-we-made-a-django-release-notes-comparison-tool-5fmj</link>
      <guid>https://dev.to/codereviewdoctor/why-we-made-a-django-release-notes-comparison-tool-5fmj</guid>
      <description>&lt;p&gt;Django release notes are great. Very detailed. However, when updating across multiple releases such as 3.1.3 to 3.1.6 one would need to open the release notes for all 3 releases to get an idea of the changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://django.readthedocs.io/en/3.1.x/releases/3.1.6.html"&gt;https://django.readthedocs.io/en/3.1.x/releases/3.1.6.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://django.readthedocs.io/en/3.1.x/releases/3.1.5.html"&gt;https://django.readthedocs.io/en/3.1.x/releases/3.1.5.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://django.readthedocs.io/en/3.1.x/releases/3.1.4.html"&gt;https://django.readthedocs.io/en/3.1.x/releases/3.1.4.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why we made the Django release notes comparison productivity tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YPmFChQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slmvs5is3cscfp54zler.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YPmFChQ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slmvs5is3cscfp54zler.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At a glance we can see the total changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsed to JSON
&lt;/h2&gt;

&lt;p&gt;To achieve this we parsed Django's release notes to JSON.&lt;/p&gt;

&lt;p&gt;An advantage of this functionality is the data is nice and machine readable. During the parsing we pull out metadata such as &lt;em&gt;how many Django bugs are there in a particular Django Release&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EiP5mlDu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ske7sr1n9dolap2ainxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EiP5mlDu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ske7sr1n9dolap2ainxz.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives some context when we see a new Django version is available: do we need to upgrade now or can it wait.&lt;/p&gt;

&lt;p&gt;Have a look: &lt;a href="https://django.doctor/compare-django-release-notes/3.0.0/3.1.6"&gt;https://django.doctor/compare-django-release-notes/3.0.0/3.1.6&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Fixing Django anti-patterns in Sentry</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Tue, 19 Jan 2021 09:32:21 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/fixing-django-anti-patterns-in-sentry-m4f</link>
      <guid>https://dev.to/codereviewdoctor/fixing-django-anti-patterns-in-sentry-m4f</guid>
      <description>&lt;p&gt;Django Doctor audits and auto fixes Django anti-patterns. We audited the &lt;a href="//sentry.io/"&gt;Sentry&lt;/a&gt; codebase for problems hindering maintainability to see how we can help, and ultimately created &lt;a href="https://github.com/getsentry/sentry/pull/23140"&gt;a Pull Request&lt;/a&gt; to fix some of them. In this post we will break down the most impactful issues, and how they can avoided.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing reverse migration
&lt;/h3&gt;

&lt;p&gt;Django data migrations have two aspects: forwards and backwards. If a developer wants to undo migrations then backwards must be specified. However, many projects have migrations that miss this because by default the skeleton data migration generated by Django only shows the forwards handler, and hence like &lt;a href="https://dev.to/djangodoctor/22-of-django-websites-can-t-roll-back-prod-thanks-to-these-2-mistakes-4cln"&gt;22% of the Django codebases we audited&lt;/a&gt;, Sentry has some data migrations that do not support undoing the migration.&lt;/p&gt;

&lt;p&gt;Unfortunately it only takes one missing reverse to make all migrations fail when attempting to undo the migrations. This does not leave the developer many options if they want to reverse migrations due to discovering a bug in prod that requires rolling back to the last good release.&lt;/p&gt;

&lt;p&gt;Thus in all the affected data migrations in the &lt;a href="https://github.com/getsentry/sentry/pull/23140"&gt;Sentry Pull Request&lt;/a&gt; we suggested the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b1F4CoNt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/e3jbcfz9wxhw13d8vyhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b1F4CoNt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/e3jbcfz9wxhw13d8vyhu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These migration changes will allow a developer to migrate backwards without Django throwing an exception, and one day may save a developer great headache when they are trying to recovery from a disaster in prod that requires rolling back and undoing a migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking queryset truthiness:
&lt;/h3&gt;

&lt;p&gt;Querysets are &lt;a href="https://docs.djangoproject.com/en/3.1/topics/db/queries/#querysets-are-lazy"&gt;lazily evaluated&lt;/a&gt; - meaning the records are not read from the database until code interact with the data. That's why we can do &lt;code&gt;queryset.all()&lt;/code&gt; without downloading every records from the database.&lt;/p&gt;

&lt;p&gt;However, if some code does &lt;code&gt;if queryset&lt;/code&gt; then the queryset is evaluated immediately: all records in the queryset are read from the database. Perhaps tens, perhaps thousands, perhaps more. Checking if a queryset is truthy/falsey is much less efficient than checking &lt;code&gt;queryset.exists()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Like &lt;a href="https://dev.to/djangodoctor/666-django-projects-checked-for-inefficient-database-queries-over-half-had-these-4-anti-patterns-4383"&gt;50% of the codebases we checked&lt;/a&gt;, Sentry suffers this anti-pattern. However, the fix is simple once the problem is identifier: in the &lt;a href="https://github.com/getsentry/sentry/pull/23140"&gt;Sentry Pull Request&lt;/a&gt; we suggested the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyZWDS0L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b95ofgssfy0udm47hm51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyZWDS0L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b95ofgssfy0udm47hm51.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also noteworthy is there were cases when the queryset was being evaluated for performance gains: it's perfectly valid to evaluate the queryset if the data in the queryset will be consumed by some code later on: no need to perform two database calls then one can suffice.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL anti-patterns
&lt;/h3&gt;

&lt;p&gt;Like &lt;a href="https://dev.to/djangodoctor/40-of-django-projects-have-these-url-bugs-waiting-to-happen-218c"&gt;40% of the Django projects we checked&lt;/a&gt;, Sentry have some URL related anti-patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate URL names &lt;a href="https://django.doctor/advice/C6001"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;hard-coded URLs in templates instead of using url template tag &lt;a href="https://django.doctor/advice/C7000"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;hard-coded static asset URLs instead of using the static template tag &lt;a href="https://django.doctor/advice/C7001"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Duplicate URL name:
&lt;/h3&gt;

&lt;p&gt;Django provides a very convenient method of generating URLs: giving each URL a name and then allowing the developer to use the &lt;a href="https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#url"&gt;&lt;code&gt;{% url&lt;/code&gt; template tag&lt;/a&gt; or &lt;a href="https://docs.djangoproject.com/en/3.1/ref/urlresolvers/#reverse"&gt;&lt;code&gt;reverse&lt;/code&gt;&lt;/a&gt; to generate the URL.&lt;/p&gt;

&lt;p&gt;But what happens if two URLs share the same name? For example &lt;a href="https://github.com/getsentry/sentry/blob/d1055cf5b09b9b591535da67bc9753d9b24e2c39/src/sentry/web/urls.py#L645"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/getsentry/sentry/blob/master/src/sentry/web/urls.py#L468"&gt;here&lt;/a&gt; both have the name &lt;code&gt;'sentry-project-event-redirect'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It will mean half of the time templates will be linking to the wrong place: if a template tries to link to the login view via &lt;code&gt;{% url "sentry-project-event-redirect"&lt;/code&gt; can you be sure it's going to the right place? No.&lt;/p&gt;

&lt;p&gt;How did this happen? Well it's easily missed when reading the code. The urls.py is over 600 lines of very dense code. Easy to not see the needle in the haystack without tools to help. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hard-coded URL
&lt;/h3&gt;

&lt;p&gt;Hard-coding URLs in templates makes changing URLs harder, and makes linking to the wrong page easier - so harms maintainability. Sentry had &lt;a href="https://github.com/getsentry/sentry/blob/d1055cf5b09b9b591535da67bc9753d9b24e2c39/src/sentry/templates/sentry/bases/auth.html#L13"&gt;one case&lt;/a&gt; of hard-coding a URL, which is very good: only one case in the huge codebase and that only was was pointing to the homepage, which is unlikely to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard-coded static asset URLs
&lt;/h3&gt;

&lt;p&gt;Django documentation suggests not hard-coding static URLs, and instead using the &lt;a href="https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#static"&gt;&lt;code&gt;{% static&lt;/code&gt; template tag&lt;/a&gt;. &lt;code&gt;{% static ...&lt;/code&gt; returns the path the browser can use to request the file. At it's simplest, that would return a path that looks up the file on your local file system. That's fine for your local dev environment but in prod we will likely use &lt;a href="https://djangopackages.org/grids/g/storage-backends/"&gt;third-party libraries&lt;/a&gt; such as &lt;a href="http://whitenoise.evans.io/en/stable/django.html"&gt;whitenoise&lt;/a&gt; or &lt;a href="https://github.com/jschneier/django-storages"&gt;django-storages&lt;/a&gt; to improve performance of the production web server.&lt;/p&gt;

&lt;p&gt;We saw three instances of hard-coded static URLs, all of them in the &lt;a href="https://github.com/getsentry/sentry/blob/d1055cf5b09b9b591535da67bc9753d9b24e2c39/src/sentry/static/sentry/app/index.html#L8"&gt;same file&lt;/a&gt;. These hinder maintainability in a few ways. The key one is if the &lt;code&gt;STORAGE_BACKEND&lt;/code&gt; is changed to one optimizing for speed of static file delivery then the following can happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File revving can change the filename &lt;/li&gt;
&lt;li&gt;Serving from S3 (or similar) can change the domain the file is served from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore hard-coding the static URL would break if the &lt;code&gt;STORAGE_BACKEND&lt;/code&gt; was changed to e.g, &lt;a href="http://whitenoise.evans.io/en/stable/django.html"&gt;whitenoise&lt;/a&gt; or &lt;a href="https://github.com/jschneier/django-storages"&gt;django-storages&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This issue is discussed on greater detail &lt;a href="https://dev.to/djangodoctor/40-of-django-projects-have-these-url-bugs-waiting-to-happen-218c#2-hard-coded-static-assets"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does your Django codebase have these issues?
&lt;/h3&gt;

&lt;p&gt;Over time it's easy for tech debt to slip into your codebase. I can check that for you at &lt;a href="https://django.doctor"&gt;django.doctor&lt;/a&gt;, or can &lt;a href="https://github.com/marketplace/django-doctor/"&gt;review your GitHub PRs&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x29KefkG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d10epulva9d373jgldhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x29KefkG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d10epulva9d373jgldhu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or try out &lt;a href="https://django.doctor/challenge/models"&gt;Django refactor challenge&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>codequality</category>
    </item>
    <item>
      <title>53% of Django projects have redundant code</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Thu, 14 Jan 2021 17:36:46 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/53-of-django-projects-have-redundant-code-594l</link>
      <guid>https://dev.to/codereviewdoctor/53-of-django-projects-have-redundant-code-594l</guid>
      <description>&lt;p&gt;Django Doctor audits code and auto fixes Django anti-patterns. We checked 666 Django projects for problems hindering maintainability and found that &lt;strong&gt;53% of the Django projects had redundant settings and model field kwargs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;33% of settings.py had redundant values &lt;a href="https://django.doctor/advice/C4005" rel="noopener noreferrer"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;29% had fields in models.py with redundant kwargs &lt;a href="https://django.doctor/advice/C2002" rel="noopener noreferrer"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were some intersections - so some projects fell into more than one camp.&lt;/p&gt;

&lt;p&gt;By "redundant" we mean the values did not need to be explicitly stated because they're only specifying the value Django would have used by default. Like a driving instructor saying "instead of doing 20mph, please do 20mph." This redundancy makes it hard to spot the signal in the noise - so it's harder for developers to zone in on the important code.&lt;/p&gt;

&lt;p&gt;But is redundancy noise? Probably, and in the sample we checked it looks like the redundancy was not on purpose. Some justify it with the &lt;a href="https://en.wikipedia.org/wiki/Zen_of_Python" rel="noopener noreferrer"&gt;Zen of Python&lt;/a&gt; "Explicit is better than implicit" - but that does not seem to be the reason why 33% over half of projects did this, because if this was the motivation then shouldn't they be explicitly defining &lt;em&gt;all&lt;/em&gt; the Django defaults and &lt;em&gt;all&lt;/em&gt; the default field kwargs, not just one or two? Additionally, the Zen of Python also suggests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple is better than complex&lt;/li&gt;
&lt;li&gt;beautiful is better than ugly.&lt;/li&gt;
&lt;li&gt;readability counts&lt;/li&gt;
&lt;li&gt;sparse is better than dense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the the reason for having redundant settings seems to often be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a result of copying and pasting settings from other projects; or&lt;/li&gt;
&lt;li&gt;historically non-default values were later updated, and instead of deleting and using the Django default the dev simply changed &lt;code&gt;True&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Things slip though the net during code review. Most people do not know every single Django default setting value as they change over the many versions of Django.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;How would you reduce noise? Try &lt;a href="https://django.doctor/challenge/models" rel="noopener noreferrer"&gt;our Django models.py refactor challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Redundant settings
&lt;/h3&gt;

&lt;p&gt;Django ships with &lt;a href="https://github.com/django/django/blob/master/django/conf/global_settings.py" rel="noopener noreferrer"&gt;default settings&lt;/a&gt;. When &lt;code&gt;django.conf.settings&lt;/code&gt; is imported we're not getting a module, we're getting an instance of &lt;a href="https://github.com/django/django/blob/master/django/conf/__init__.py#L49" rel="noopener noreferrer"&gt;&lt;code&gt;LazySettings&lt;/code&gt;&lt;/a&gt;. This object is a wrapper around a "holder" of the actual settings. During the normal running of Django the holder first uses the default settings then ingests the values in the file defined by the environment variable &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So explicitly setting Django's default values in settings.py does not change how Django operates, but it is additional lines of code the developers must read when maintaining the code. The cost of each extra line is non-zero.&lt;/p&gt;

&lt;p&gt;An implication of this is when we define values in our &lt;code&gt;settings.py&lt;/code&gt; that are already defined in Django's &lt;a href="https://github.com/django/django/blob/master/django/conf/global_settings.py" rel="noopener noreferrer"&gt;default settings&lt;/a&gt; then the action is &lt;a href="https://en.wikipedia.org/wiki/Idempotence" rel="noopener noreferrer"&gt;idempotent&lt;/a&gt;. To Django, the value may as well not be there.&lt;/p&gt;

&lt;p&gt;It's easy for such things to slip through the net when a human is reviewing the code because this is a nice example of "searching for a needle in a hackstack", but Django Doctor is a code review bot so cannot miss it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F01f2kwcipg8advbkx07h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F01f2kwcipg8advbkx07h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Redundant field kwargs
&lt;/h3&gt;

&lt;p&gt;Similar to Django's default settings, Django's model fields have &lt;a href="https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-options" rel="noopener noreferrer"&gt;default kwargs&lt;/a&gt; that can be overridden when models area defined. Most of the points highlighted in the previous section are valid here so no need to cover them again.&lt;/p&gt;

&lt;p&gt;29% of the projects we checked had explicitly defined kwargs, with &lt;code&gt;null=False&lt;/code&gt; being the overwhelming most common one, but &lt;a href="https://docs.djangoproject.com/en/3.1/ref/models/fields/#null" rel="noopener noreferrer"&gt;&lt;code&gt;null&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt; by default&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;So instead of&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;some_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&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;null&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;p&gt;We can simplify things and do:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;some_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Models are often dense enough even without redundant kwargs. Less dense models will improve the readability of the file and make developers' lives that little bit better.&lt;/p&gt;

&lt;p&gt;So like &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;&lt;em&gt;Don't Repeat Yourself&lt;/em&gt; (DRY)&lt;/a&gt;, we can say &lt;em&gt;Don't Repeat Django (DRD)&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Could your Django be simplified?
&lt;/h3&gt;

&lt;p&gt;Half of Django codebases can be simplified, and it’s easy to miss something during code review. I can check that for you at &lt;a href="https://django.doctor" rel="noopener noreferrer"&gt;django.doctor&lt;/a&gt;, or can &lt;a href="https://github.com/marketplace/django-doctor/" rel="noopener noreferrer"&gt;review your GitHub PRs&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd10epulva9d373jgldhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd10epulva9d373jgldhu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or try out &lt;a href="https://django.doctor/challenge" rel="noopener noreferrer"&gt;Django challenges&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>codequality</category>
    </item>
    <item>
      <title>48% of Django projects could simplify models.py in these 3 ways</title>
      <dc:creator>Code Review Doctor</dc:creator>
      <pubDate>Tue, 12 Jan 2021 16:12:45 +0000</pubDate>
      <link>https://dev.to/codereviewdoctor/48-of-django-projects-could-simplify-models-py-in-these-3-ways-1anc</link>
      <guid>https://dev.to/codereviewdoctor/48-of-django-projects-could-simplify-models-py-in-these-3-ways-1anc</guid>
      <description>&lt;p&gt;Django Doctor audits code and auto fixes Django anti-patterns. We checked 666 Django projects for problems hindering maintainability and found that &lt;strong&gt;48% of the Django projects could simplify their models.py&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;22% used &lt;code&gt;Charfield&lt;/code&gt; with huge &lt;code&gt;max_length&lt;/code&gt; when a &lt;code&gt;TextField&lt;/code&gt; would be better &lt;a href="https://django.doctor/advice/C2006" rel="noopener noreferrer"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;7% used deprecated &lt;code&gt;NullBooleanField&lt;/code&gt; &lt;a href="https://django.doctor/advice/C2008" rel="noopener noreferrer"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;40% had string fields that allowed &lt;code&gt;null&lt;/code&gt; but not &lt;code&gt;blank&lt;/code&gt; or vice versa &lt;a href="https://django.doctor/advice/C2003" rel="noopener noreferrer"&gt;read more&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were some intersections - so some projects fell into more than one camp. Note there are valid usecases for &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;blank&lt;/code&gt; differing, and we go through that in depth later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How would you simply Models? Try &lt;a href="https://django.doctor/challenge/models" rel="noopener noreferrer"&gt;our Django models.py refactor challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. CharField with huge max_length
&lt;/h3&gt;

&lt;p&gt;When a user needs to enter a string that may be very long then it's quick and easy to use &lt;code&gt;CharField(max_length=5001)&lt;/code&gt;, but that has some problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5001 is big, but is it big enough? What if the a user wants  more? Yes &lt;code&gt;max_length&lt;/code&gt; can be increased, but bug fixes are a pain, as is the database migration to facilitate the change.&lt;/li&gt;
&lt;li&gt;Years from now a developer dusts off your code and reads the number 5001. Do they infer there's something special about 5001? Maybe. Devs in the future will be very busy with complicated future things so let's not add ambiguity when maintaining your "old" code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;TextField&lt;/code&gt; is better here as there's really no need for a length check, so users will not be presented with a validation error. A nice rule of thumb cane be &lt;em&gt;if the field does not need minimum length check then it probably does not need a maximum length check&lt;/em&gt;. For that reason when I see this happening I suggest using &lt;code&gt;TextField&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbgxhys3mbesw4or05ly4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbgxhys3mbesw4or05ly4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So why use &lt;code&gt;CharField&lt;/code&gt; if &lt;code&gt;TextField&lt;/code&gt; is so great? Historically efficient database space usage was a key consideration. But now storage is practically free. Plus, for &lt;a href="https://www.postgresql.org/docs/9.0/datatype-character.html" rel="noopener noreferrer"&gt;Postgres&lt;/a&gt; at least, using &lt;code&gt;TextField&lt;/code&gt; has the same performance as &lt;code&gt;CharField&lt;/code&gt;, so database storage performance is not a key consideration.&lt;/p&gt;

&lt;p&gt;There are valid cases for &lt;code&gt;CharField&lt;/code&gt; with a huge length though: just like an &lt;a href="https://en.wikipedia.org/wiki/International_Standard_Book_Number" rel="noopener noreferrer"&gt;ISBN&lt;/a&gt; is always 10 or 13 characters, there are some very long codes. Storing &lt;a href="https://en.wikipedia.org/wiki/QR_code" rel="noopener noreferrer"&gt;QR codes&lt;/a&gt;? &lt;code&gt;CharField&lt;/code&gt;. Working with geometry and geo spacial? &lt;code&gt;CharField&lt;/code&gt;. &lt;a href="https://github.com/django/django/blob/f06beea92999407cc8dad3c47f006b7c727095a6/django/contrib/gis/db/backends/postgis/models.py#L60" rel="noopener noreferrer"&gt;Django GIS&lt;/a&gt; has a 2048 long &lt;code&gt;VARCHAR&lt;/code&gt; that Django represents as a &lt;code&gt;CharField(max_length=2048)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Deprecated NullBooleanField
&lt;/h3&gt;

&lt;p&gt;For four years the documentation for &lt;code&gt;NullBooleanField&lt;/code&gt; was two sentences and one of was "yeah…don't use it". As of 3.1 the axe has fallen and the hint of future deprecation has been replaced with an actual deprecation warning. Instead of using &lt;code&gt;NullBooleanField()&lt;/code&gt; use &lt;code&gt;BooleanField(null=True)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For that reason when I see &lt;code&gt;NullBooleanField&lt;/code&gt; I suggest using &lt;code&gt;BooleanField(null=True)&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fekwwi5kp3p8et8be3133.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fekwwi5kp3p8et8be3133.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the face of it, the existence of &lt;code&gt;NullBooleanField&lt;/code&gt; seems odd. Why have an entire class that can be achieved with a &lt;code&gt;null&lt;/code&gt; keyword argument? We don't see &lt;code&gt;NullCharField&lt;/code&gt; or &lt;code&gt;NullDateField&lt;/code&gt;. Indeed, for those Django expects us to do &lt;code&gt;CharField(null=True)&lt;/code&gt; and &lt;code&gt;DateField(null=True)&lt;/code&gt;. So what's was so special about &lt;code&gt;NullBooleanField&lt;/code&gt; and why is it now deprecated?&lt;/p&gt;

&lt;h4&gt;
  
  
  Enter NullBooleanField
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;NullBooleanField&lt;/code&gt; renders a &lt;code&gt;NullBooleanSelect&lt;/code&gt; widget which is a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; containing options "Unknown" (None), "Yes" (True) and "No" (False). The implication is &lt;code&gt;NullBooleanField&lt;/code&gt; was intended for when explicitly stating no answer is known yet. Indeed, in many contexts it would be useful to clarify "is it False because the user has set it False, or because the user has not yet answered?". To facilitate that, the database column must allow &lt;code&gt;NULL&lt;/code&gt; (&lt;code&gt;None&lt;/code&gt; at Python level).&lt;/p&gt;

&lt;p&gt;Unfortunately time has shown a great deal of room for confusion: StackOverflow has many questions that are answered with "use &lt;code&gt;NullBooleanField&lt;/code&gt; instead of &lt;code&gt;BooleanField&lt;/code&gt;" and vice versa. If one of the reasons for separating &lt;code&gt;BooleanField&lt;/code&gt; and &lt;code&gt;NullBooleanField&lt;/code&gt; was to give clarity then instead the opposite occurred for many.&lt;/p&gt;

&lt;h4&gt;
  
  
  Exit NullBooleanField
&lt;/h4&gt;

&lt;p&gt;Until Django 2.1 in 2018, null was not permitted in &lt;code&gt;BooleanField&lt;/code&gt; because (obviously) &lt;code&gt;None&lt;/code&gt; is not in a &lt;code&gt;bool&lt;/code&gt; value. Why would we expect &lt;code&gt;None&lt;/code&gt; to be used in a field that says it's for boolean values only? On the other hand &lt;code&gt;None&lt;/code&gt; is not a &lt;code&gt;str&lt;/code&gt; either but &lt;code&gt;CharField(null=True)&lt;/code&gt; was supported and &lt;code&gt;None&lt;/code&gt; is not an &lt;code&gt;int&lt;/code&gt;, but &lt;code&gt;IntegerField(null=True)&lt;/code&gt; was also acceptable.&lt;/p&gt;

&lt;p&gt;So in the deprecation of &lt;code&gt;NullBooleanField&lt;/code&gt; there is an argument for consistency with how the other fields handle null. If we're aiming for consistency the choice is to either add &lt;code&gt;NullCharField&lt;/code&gt;, &lt;code&gt;NullIntegerField&lt;/code&gt;, &lt;code&gt;NullDateField&lt;/code&gt; and so on or to rename &lt;code&gt;NullBooleanField&lt;/code&gt; to &lt;code&gt;BooleanField&lt;/code&gt; and call it a day, even though &lt;code&gt;NullBooleanField&lt;/code&gt; was a more accurate name.&lt;/p&gt;

&lt;p&gt;With this deprecation three classes are impacted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;django.models.fields.NullBooleanField&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;django.forms.fields.BooleanField&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;django.forms.widgets.NullBooleanSelect&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three have slightly different handling of "empty" values, so for some the swap from &lt;code&gt;NullBooleanField&lt;/code&gt; to &lt;code&gt;BooleanField&lt;/code&gt; will need some careful testing:&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;from&lt;/span&gt; &lt;span class="n"&gt;django.forms.fields&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NullBooleanField&lt;/span&gt;

&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NullBooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.forms.fields&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BooleanField&lt;/span&gt;

&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&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="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;

&lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&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;blank&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="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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;test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean&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="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="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Null and blank out of sync
&lt;/h3&gt;

&lt;p&gt;Expect the unexpected if &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;blank&lt;/code&gt; are different values: &lt;code&gt;null&lt;/code&gt; controls if the the database level validation allows no value for the field, while blank controls if the application level validation allows no value for the field.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;blank=True&lt;/code&gt; then the field model validation allows an empty value such as &lt;code&gt;""&lt;/code&gt; to be inputted by users. If &lt;code&gt;blank=False&lt;/code&gt; then the validation will prevent empty values being inputted.&lt;/p&gt;

&lt;p&gt;On the other hands, &lt;code&gt;null&lt;/code&gt; informs the database if the database column for the field can be left empty, resulting in the database setting either &lt;code&gt;NULL&lt;/code&gt; or &lt;code&gt;NOT NULL&lt;/code&gt; on the column. If the database encounters an empty &lt;code&gt;NOT NULL&lt;/code&gt; column then it will raise an &lt;code&gt;IntegrityError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;blank&lt;/code&gt; is used during during field validation. &lt;code&gt;Form&lt;/code&gt;, &lt;code&gt;ModelForm&lt;/code&gt;, and &lt;code&gt;ModelSerializer&lt;/code&gt; each trigger field level validation. For a concrete example, &lt;code&gt;ModelForm&lt;/code&gt; calls the model instance's &lt;code&gt;full_clean&lt;/code&gt; method during form validation, and full_clean then calls &lt;code&gt;clean_fields&lt;/code&gt;, which in turn may raise a &lt;code&gt;ValidationError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For that reason when I see this happening I suggest the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl7fu7wp5giccdbjjulre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl7fu7wp5giccdbjjulre.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So normally do we want &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;blank&lt;/code&gt; to the same value?  When would we want to have &lt;code&gt;null=False&lt;/code&gt; and &lt;code&gt;blank=True&lt;/code&gt; or even &lt;code&gt;null=True&lt;/code&gt; and &lt;code&gt;blank=False&lt;/code&gt;?&lt;/p&gt;

&lt;h4&gt;
  
  
  null=False, blank=True
&lt;/h4&gt;

&lt;p&gt;This facilitates using sensible default values for string fields: the field may have a default value like &lt;code&gt;name = CharField(null=False, blank=True, default="")&lt;/code&gt;. This is useful if the field is optional, but we also want to prevent the database column from having inconsistent data types. Sometimes being None, sometimes being &lt;code&gt;""&lt;/code&gt;, and other times being a non-empty string causes extra complexity in code and in ORM: if we wanted to find all users with no name:&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;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name__isnull&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that with the case for when the value in the database column will always be a string:&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;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  null=True, blank=False
&lt;/h4&gt;

&lt;p&gt;This scenario is more to keep the database happy. If using the &lt;code&gt;django.db.backends.oracle&lt;/code&gt; database engine then this may be needed because Oracle forces empty strings to &lt;code&gt;NULL&lt;/code&gt;, even if an empty string was submitted in the form, so &lt;code&gt;name = CharField(null=True, blank=False)&lt;/code&gt; would be needed.&lt;/p&gt;

&lt;p&gt;Zero downtime deployment strategies may required &lt;code&gt;NULL&lt;/code&gt; on the database column, even though business requirements dictate the user must enter a value in the form. During blue/green deployments both the new codebase and the old codebase run against the same database at the same. If the new codebase adds a new fields and there is no sensible default value for it then &lt;code&gt;null=True&lt;/code&gt; is needed to avoid the database throwing an &lt;code&gt;IntegrityError&lt;/code&gt; while the instance of your website running the old codebase interacts with the database.&lt;/p&gt;

&lt;p&gt;While the database column can accept &lt;code&gt;null&lt;/code&gt;, form validation can prevent the end users inputting no value, so data type consistency is assured? No - this required the form validation to actually run. If a developer is creating or updating via the shell then the validation will not run unless the developer calls &lt;code&gt;instance.full_clean()&lt;/code&gt; or &lt;code&gt;instance.clean_fields()&lt;/code&gt;. This strategy is simplified if a sane default value can be used instead of setting &lt;code&gt;null=True&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Could your models.py be simplified?
&lt;/h3&gt;

&lt;p&gt;I can check that for you at &lt;a href="https://django.doctor" rel="noopener noreferrer"&gt;django.doctor&lt;/a&gt;, or can &lt;a href="https://github.com/marketplace/django-doctor/" rel="noopener noreferrer"&gt;review your GitHub PRs&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd10epulva9d373jgldhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd10epulva9d373jgldhu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or try out &lt;a href="https://django.doctor/challenge" rel="noopener noreferrer"&gt;Django refactor challenges&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>codequality</category>
    </item>
  </channel>
</rss>
