<?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>Forem: Chris White</title>
    <description>The latest articles on Forem by Chris White (@cwprogram).</description>
    <link>https://forem.com/cwprogram</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%2F1062157%2F0c8868ac-bf70-4677-b83b-04c08195770b.jpeg</url>
      <title>Forem: Chris White</title>
      <link>https://forem.com/cwprogram</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cwprogram"/>
    <language>en</language>
    <item>
      <title>The Great DNS Trail</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Mon, 27 Apr 2026 01:55:40 +0000</pubDate>
      <link>https://forem.com/cwprogram/the-great-dns-trail-1fa8</link>
      <guid>https://forem.com/cwprogram/the-great-dns-trail-1fa8</guid>
      <description>&lt;p&gt;DNS or domain naming system is commonly utilized to allow websites the ability to be represented by a specific name. We'll be looking at the underlying parts of DNS in some detail to see much of what is normally abstracted away.&lt;/p&gt;

&lt;h2&gt;
  
  
  IP Addresses
&lt;/h2&gt;

&lt;p&gt;In order to understand DNS, it's important to first understand what DNS is mapped to. A domain name by itself can't do anything which is why DNS exists in the first place. The actual means of accessing another system is done through IP addresses. As an example if I ping the dev.to site the address &lt;code&gt;151.101.66.217&lt;/code&gt; comes back. &lt;/p&gt;

&lt;p&gt;Now on the low level IP addresses are a sequences of bytes and the &lt;code&gt;.&lt;/code&gt;s are mostly for visual purposes acting much like a comma in a sequences of numbers. If you were to use 32 bit binary the address &lt;code&gt;151.101.66.217&lt;/code&gt; would come back as &lt;code&gt;10010111011001010100001011011001&lt;/code&gt;. Unlike domain names IP addresses are very compact and streamlined for quick network transport.&lt;/p&gt;

&lt;p&gt;IP addresses are also bound by a numbering authority ICANN (Internet Corporation for Assigned Names and Numbers). This authority handles registration of specific IP ranges to corporations. &lt;code&gt;151.101.66.217&lt;/code&gt; is part of the IP range &lt;code&gt;151.101.0.0 - 151.101.255.255&lt;/code&gt; which according to &lt;a href="https://lookup.icann.org/en" rel="noopener noreferrer"&gt;ICANN's lookup&lt;/a&gt; belongs to the CDN (Content Distribution Network) provider &lt;a href="https://www.fastly.com/" rel="noopener noreferrer"&gt;Fastly&lt;/a&gt;. Note that there are special ranges of IPs known as private IPs. These sit behind routers and firewalls providing local network functionality. &lt;/p&gt;

&lt;h2&gt;
  
  
  Hosts File
&lt;/h2&gt;

&lt;p&gt;Now before even touching DNS there's a quick check against what's known as the hosts file. On most systems this lies at &lt;code&gt;/etc/hosts&lt;/code&gt; and windows it can be found at &lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt;. The file looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# localhost name resolution is handled within DNS itself.
#   127.0.0.1       localhost
#   ::1             localhost
&lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;91&lt;/span&gt; &lt;span class="n"&gt;rpi&lt;/span&gt;
&lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;93&lt;/span&gt; &lt;span class="n"&gt;rpi2&lt;/span&gt;
&lt;span class="c"&gt;# End of section
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is a mapping of my raspberry pi devices to their respective IP addresses on my local network. Instead of typing the IP address I can simply type &lt;code&gt;rpi&lt;/code&gt; when SSH-ing in. Unlike actual domain names host file entries are more free form. Some advanced uses have domain names mapped to &lt;code&gt;0.0.0.0&lt;/code&gt; providing a rudimentary domain blacklist. &lt;/p&gt;

&lt;h2&gt;
  
  
  DNS Cache
&lt;/h2&gt;

&lt;p&gt;There's another entry point before DNS infrastructure is actually involved and that is DNS cache. Operating systems will vary in how they handle cache. Linux in most cases doesn't have DNS caching on by default due to its need to support a number of different scenarios. On Windows the cache may look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;www.google-analytics.com
----------------------------------------
Record Name . . . . . : www.google-analytics.com
Record Type . . . . . : 1
Time To Live  . . . . : 9
Data Length . . . . . : 4
Section . . . . . . . : Answer
A (Host) Record . . . : 142.250.178.14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsers can also have their own built in DNS due to the large number of requests they typically make. Here's an example of my Firefox entry for dev.to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdwb6hzgrqbfpyeblo5sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdwb6hzgrqbfpyeblo5sr.png" alt="An excerpt showing the DNS cache entry in Firefox for dev.to"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there caching works up several levels including routers, ISP/public servers, all the way up the chain. &lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Name Authorities
&lt;/h2&gt;

&lt;p&gt;Domain names have a hierarchical system of authorities starting at what are known as the root servers all the way down to the authoritative server that owns the domain name. This server is generally the registrar used to purchase the domain name. &lt;/p&gt;

&lt;p&gt;IANA (Internet Assigned Numbers Authority) is a part of an ICANN affiliate which handles root DNS servers. These servers are the absolute authority on what are known as the top level domains. They keep a listing along with the entity who is authorized to represent the top level domain. For example, VeriSign Global Registry Service is the entity which is authorized to register domain names for the &lt;code&gt;.com&lt;/code&gt; top level domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.com    generic     VeriSign Global Registry Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system also distinguishes between such generic top level domains and ones designated for countries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.us     country-code    Registry Services, LLC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most companies will go through the &lt;a href="https://newgtldprogram.icann.org/en" rel="noopener noreferrer"&gt;generic top level domain program&lt;/a&gt; if they themselves wish to act as an authority. There's also &lt;a href="https://www.icann.org/forms/idn_cctld" rel="noopener noreferrer"&gt;another process&lt;/a&gt; for top level domains representing countries. For those who wish to act as a domain registrar instead of a top level authority &lt;a href="https://www.icann.org/en/contracted-parties/accredited-registrars/how-to-become-a-registrar" rel="noopener noreferrer"&gt;the accredited registrar program is available&lt;/a&gt;. Given how critical DNS infrastructure is all of these applications are strictly vetted so it's certainly a large investment. &lt;/p&gt;

&lt;h2&gt;
  
  
  The DNS Flow
&lt;/h2&gt;

&lt;p&gt;So assuming the answer to a query of what something like &lt;code&gt;dev.to&lt;/code&gt; is mainly starts at the locally designated DNS servers. This will most likely be your ISP but it's not uncommon to utilize public DNS servers such as &lt;a href="https://quad9.net/" rel="noopener noreferrer"&gt;Quad9&lt;/a&gt;, &lt;a href="https://developers.google.com/speed/public-dns" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, and &lt;a href="https://www.cloudflare.com/application-services/products/dns/" rel="noopener noreferrer"&gt;Cloud Flare&lt;/a&gt;. Public DNS servers were popularized after "certain ISPs" decided that &lt;a href="https://arstechnica.com/tech-policy/2009/08/comcasts-dns-redirect-service-goes-nationwide/" rel="noopener noreferrer"&gt;showing ad littered pages for unknown domains&lt;/a&gt; was a good idea.&lt;/p&gt;

&lt;p&gt;So let's say I'm looking for &lt;code&gt;dev.to&lt;/code&gt;. Assuming it isn't cached my designated DNS server will reach out to one of the root DNS servers (&lt;code&gt;h.root-servers.net&lt;/code&gt; for example) to get information on &lt;code&gt;.to&lt;/code&gt;'s ownership. &lt;code&gt;.to&lt;/code&gt; is country code tld managed by the Kingdom of Tonga. It's not uncommon for country based TLDs to be used for vanity purposes such as &lt;code&gt;.ai&lt;/code&gt;. The &lt;code&gt;.to&lt;/code&gt; authority (&lt;code&gt;ns01.trs-dns.com&lt;/code&gt; as an example) will then delegate the request to the authoritative nameservers which in this case are owned by Cloud Flare who would be considered to be the registrar that &lt;code&gt;dev.to&lt;/code&gt; worked with to obtain (or transfer) the domain (&lt;code&gt;josh.ns.cloudflare.com&lt;/code&gt; for example). This will then provide a number of responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; ANSWER SECTION:
&lt;span class="go"&gt;│dev.to.                 300     IN      A       151.101.194.217
│dev.to.                 300     IN      A       151.101.130.217
│dev.to.                 300     IN      A       151.101.66.217
│dev.to.                 300     IN      A       151.101.2.217
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any of these addresses will allow access to the &lt;code&gt;dev.to&lt;/code&gt; site. Sometimes the entry comes back as a &lt;code&gt;CNAME&lt;/code&gt; which is essentially an alias to another domain. In this case that will form a new request for the user's designated DNS server. All of these responses will be cached appropriately to ensure timely return to the client. &lt;/p&gt;

&lt;p&gt;It's important to note that the user's DNS query to the ISP/Public DNS server is what's known as a recursive query as the server is handling the entire chain for you. The process of the ISP/Public DNS server going root server -&amp;gt; tld server -&amp;gt; authoritative server is known as incremental lookup. Note that if you attempt to query a root name server (recursive by default) it will simply let you know it doesn't support recursive calls and return the incremental version instead. &lt;/p&gt;

&lt;h2&gt;
  
  
  DNS Packets
&lt;/h2&gt;

&lt;p&gt;DNS utilizes UDP (user datagram protocol) instead of the traditional TCP/IP used by many applications. The UDP protocol is extremely simple with fields for source port, destination port, length of packet, checksum, and data. This avoids the overhead of many of TCP's features which means packets would take longer to return. UDP is generally wrapped around one of the IP protocols to indicate the source and destination IP. DNS packet data is built around messages as defined in &lt;a href="https://datatracker.ietf.org/doc/html/rfc1035" rel="noopener noreferrer"&gt;RFC 1035&lt;/a&gt;. By using the &lt;code&gt;dig&lt;/code&gt; DNS tool we can see some of what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dig dev.to
&lt;span class="go"&gt;│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; DiG 9.20.21-1~deb13u1-Debian &amp;lt;&amp;lt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; dev.to
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; global options: +cmd
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; Got answer:
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; -&amp;gt;&amp;gt;HEADER&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;opcode&lt;/span&gt;&lt;span class="sh"&gt;: QUERY, status: NOERROR, id: 41311
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="sh"&gt;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
&lt;/span&gt;&lt;span class="go"&gt;│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; OPT PSEUDOSECTION:
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;EDNS: version: 0, flags:&lt;span class="p"&gt;;&lt;/span&gt; udp: 4096
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; QUESTION SECTION:
&lt;span class="gp"&gt;│;&lt;/span&gt;dev.to.                                IN      A
&lt;span class="go"&gt;│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; ANSWER SECTION:
&lt;span class="go"&gt;│dev.to.                 261     IN      A       151.101.194.217
│dev.to.                 261     IN      A       151.101.66.217
│dev.to.                 261     IN      A       151.101.2.217
│dev.to.                 261     IN      A       151.101.130.217
│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; Query &lt;span class="nb"&gt;time&lt;/span&gt;: 0 msec
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; SERVER: 192.168.1.254#53&lt;span class="o"&gt;(&lt;/span&gt;192.168.1.254&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;UDP&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; WHEN: Sun Apr 26 20:17:30 EDT 2026
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; MSG SIZE  rcvd: 99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I'm doing a recursive query directed at my router (which is pointed to Google's DNS servers). The router has indicated it's a server that is capable of doing recursive queries. The answer section is the resulting IPs behind &lt;code&gt;dev.to&lt;/code&gt;. Now let's compare this to another query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;dig @ns01.trs-dns.com dev.to
&lt;span class="go"&gt;│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; DiG 9.20.21-1~deb13u1-Debian &amp;lt;&amp;lt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; @ns01.trs-dns.com dev.to
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;2 servers found&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; global options: +cmd
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; Got answer:
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; -&amp;gt;&amp;gt;HEADER&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;opcode&lt;/span&gt;&lt;span class="sh"&gt;: QUERY, status: NOERROR, id: 61237
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="sh"&gt;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 1
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="sh"&gt;; WARNING: recursion requested but not available
&lt;/span&gt;&lt;span class="go"&gt;│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; OPT PSEUDOSECTION:
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;EDNS: version: 0, flags:&lt;span class="p"&gt;;&lt;/span&gt; udp: 1232
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; QUESTION SECTION:
&lt;span class="gp"&gt;│;&lt;/span&gt;dev.to.                                IN      A
&lt;span class="go"&gt;│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; AUTHORITY SECTION:
&lt;span class="go"&gt;│dev.to.                 900     IN      NS      josh.ns.cloudflare.com.
│dev.to.                 900     IN      NS      jill.ns.cloudflare.com.
│
&lt;/span&gt;&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; Query &lt;span class="nb"&gt;time&lt;/span&gt;: 91 msec
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; SERVER: 2620:57:4001::1#53&lt;span class="o"&gt;(&lt;/span&gt;ns01.trs-dns.com&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;UDP&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; WHEN: Sun Apr 26 20:03:51 EDT 2026
&lt;span class="gp"&gt;│;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; MSG SIZE  rcvd: 118
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case &lt;code&gt;dig&lt;/code&gt; is letting me know that the server doesn't support recursive queries and I'm given an incremental result instead. Because I'm not given the actual records for the domain in question the "AUTHORITY" section is populated instead pointing me to the authoritative nameservers. When the actual DNS records for the domain name comes back the "ANSWER" records are populated instead. &lt;/p&gt;

&lt;h2&gt;
  
  
  DNS Records
&lt;/h2&gt;

&lt;p&gt;There are a &lt;strong&gt;lot&lt;/strong&gt; of &lt;a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types" rel="noopener noreferrer"&gt;DNS record types&lt;/a&gt;. Which of these will be acted upon will depend on the client accessing the information. A good majority of the time the important records you'll be working with are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A: IPv4 address backing the domain&lt;/li&gt;
&lt;li&gt;AAA: IPv6 address backing the domain&lt;/li&gt;
&lt;li&gt;CNAME: points to another host to do DNS lookup for&lt;/li&gt;
&lt;li&gt;MX: Email server backing the domain's email services&lt;/li&gt;
&lt;li&gt;TXT: Freeform field used for a number of purposes&lt;/li&gt;
&lt;li&gt;NS: The authoritative name servers for the domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A and AA records are the most vital in obtaining the IP address to map to a domain name in a request. &lt;/p&gt;

&lt;h2&gt;
  
  
  DNSSEC
&lt;/h2&gt;

&lt;p&gt;Being a simple plain text protocol DNS has security issues. If a malicious actor was able to spoof a DNS response that response would be cached among the global DNS infrastructure. This is commonly known as &lt;a href="https://en.wikipedia.org/wiki/DNS_spoofing" rel="noopener noreferrer"&gt;DNS Spoofing or DNS Cache Poisoning&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In an attempt to deal with this &lt;a href="https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-05-en" rel="noopener noreferrer"&gt;DNSSEC&lt;/a&gt; was proposed. The system works off of &lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography" rel="noopener noreferrer"&gt;public key cryptography&lt;/a&gt; which is the same technology that powers SSL certs. DNS servers use cryptographic signatures to sign their data which are validated by their parent authority (&lt;code&gt;.to&lt;/code&gt;'s owner in the case of &lt;code&gt;dev.to&lt;/code&gt;). The exception being the root DNS servers who have no parent authority. It's why the private keys for the root servers are very well guarded. &lt;/p&gt;

&lt;p&gt;It's important to note that DNSSEC support is not mandatory as of right now. Though it's of course recommended to utilize it whenever possible. &lt;/p&gt;

&lt;h2&gt;
  
  
  Internal DNS
&lt;/h2&gt;

&lt;p&gt;What I've described until now is for the public facing side. Companies are also able to utilize DNS for their own internal domain name resolution. This is done by pointing company equipment at an internal DNS server (or updating network configs for VPN users). The internal server will resolve for internal hosts and generally acts as a proxy if the domain is global such as google.com. Such servers are often hosted with software such as BIND9 on *NIX based systems and the DNS components that are part of Windows Server installs. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Thanks for joining me in this very long look at the workings of DNS. I hope this proves to be an enlightening experience for those looking to deepen their knowledge on how systems work. It might be useful if you need to debug DNS issues as well! &lt;/p&gt;

</description>
      <category>networking</category>
      <category>network</category>
    </item>
    <item>
      <title>Open Source And The Contribution Cemetery</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Fri, 24 Apr 2026 15:59:31 +0000</pubDate>
      <link>https://forem.com/cwprogram/open-source-and-the-contribution-cemetery-2kbm</link>
      <guid>https://forem.com/cwprogram/open-source-and-the-contribution-cemetery-2kbm</guid>
      <description>&lt;p&gt;As someone has appreciates the value of open source, I'd like to touch on a rather troubling issue I've found. An issue which has been further made problematic with the recent explosion of AI. It's something I refer to as the contribution cemetery. &lt;/p&gt;

&lt;h2&gt;
  
  
  Thar be dragons here
&lt;/h2&gt;

&lt;p&gt;I'm going to show you some stats for a few projects on GitHub. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/psf/requests" rel="noopener noreferrer"&gt;python requests&lt;/a&gt; 145 issues 80 pull requests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/vscode" rel="noopener noreferrer"&gt;vscode&lt;/a&gt; 5k+ issues 1.8k pull requests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kubernetes/kubernetes" rel="noopener noreferrer"&gt;kubernetes&lt;/a&gt; 1.8k issues 889 pull requests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rails/rails" rel="noopener noreferrer"&gt;Ruby On Rails&lt;/a&gt; 518 issues 1.1k pull requests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rust-lang/rust" rel="noopener noreferrer"&gt;Rust Language&lt;/a&gt; 5k+ issues 1.1k pull requests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lodash/lodash" rel="noopener noreferrer"&gt;lodash&lt;/a&gt; 68 issues 72 pull requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;lodash, a popular NPM module, has what seems to be a small amount. However, 68 issues is quite a lot to go through. This is a fairly common pattern among popular open source software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The burden of maintenance
&lt;/h2&gt;

&lt;p&gt;While the code for open source projects is free and available, the cost of maintaining it is still very real. Python requests currently has 2 maintainers, though it used to have more. Some projects, such as kubernetes, have a decent amount of individuals handling the project. Yet still we have these fairly large amounts of issues and PRs. To make matters worse, AI powered PRs have caused further stress on maintaining a popular open source project. Even the Linux Kernel is &lt;a href="https://lwn.net/Articles/1068928/" rel="noopener noreferrer"&gt;starting to remove code that isn't well maintained&lt;/a&gt; due to the onslaught of security reports.&lt;/p&gt;

&lt;p&gt;Just recently I was even going to install lunarvim, a vim distribution I used a while back, only to &lt;a href="https://github.com/LunarVim/LunarVim/discussions/4518" rel="noopener noreferrer"&gt;find out it was abandoned&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributor dilemma
&lt;/h2&gt;

&lt;p&gt;So let's say there's someone who wants to contribute to one of these projects. Even if there was something to fix they would need to make sure it didn't conflict with several hundred/thousand PRs and figure out which of several hundred/thousand issues might cover it. Given the huge popularity of GitHub within development communities this seems to almost be a "if everyone can contribute no one can contribute" type situation.&lt;/p&gt;

&lt;p&gt;I'd also say it can be difficult to find projects to contribute to if you're looking for something smaller in popularity. The bigger question is if they actually need help or if it's just someone who wants to share code for educational purposes and has no interest in maintaining it. While GitHub does have a &lt;a href="https://github.com/collections" rel="noopener noreferrer"&gt;collections page&lt;/a&gt; categorizing certain repositories, I've found that many of them fall under the same issue/pr ratio. &lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;As far as what to do about the issue it's pretty difficult. Most of the solutions will vary depending on how big the community is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug wrangling
&lt;/h3&gt;

&lt;p&gt;My first exposure to open source was the Gentoo Linux project. I spent a lot of time there doing something called "bug wrangling". Essentially I would go into bug reports, try to reproduce the issue (I had access to a &lt;strong&gt;lot&lt;/strong&gt; of architectures), and try to point the maintainer in the right direction as to where the problem might be. Something like this on a larger scale could help bring issues to a more reasonable count. Larger projects may need to do it over several periods to prevent too much volatility being pushed to users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug report time outs
&lt;/h3&gt;

&lt;p&gt;While certainly not ideal, I've seen many projects have timers on when an issue will expire. Thought I'm not sure if that could easily be retrofit. Some projects may benefit from slightly shorter bug report timeouts. &lt;/p&gt;

&lt;h3&gt;
  
  
  Project restructure
&lt;/h3&gt;

&lt;p&gt;Sometimes it's simply the scale of the project. The Linux kernel, for example, supports several years worth of hardware. This monolithic design naturally increases how many issues and PRs are required to support it. Most projects would require gradual restructuring as an all and one approach would be too risky. It may also be a case of what constitutes the project vision and if they need to be more strict with it in terms of what PRs to accept.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full reset
&lt;/h3&gt;

&lt;p&gt;This would essentially be closing everything and starting from scratch. The main issue is doing it at scale and if everything would simply pile up again in numbers. Some contributors also might not be too thrilled seeing their work was closed out. There's also the question of if this could be done at scale. &lt;/p&gt;

&lt;h3&gt;
  
  
  New tooling
&lt;/h3&gt;

&lt;p&gt;Utilize a different system, such as bugzilla, to track issues. PRs would be looked at only if they had an entry in the new project issue tracker. Having an SSO login that could utilize GitHub's authentication would help alleviate the pain of having to go to a separate site for issues. It could also be something that layers on top of GitHub issues/PRs with an easier to digest UI&lt;/p&gt;

&lt;h3&gt;
  
  
  Team expansion
&lt;/h3&gt;

&lt;p&gt;Expand project teams to include people who just handle issue cleanup, more maintainers to approve PRs, expand linting to catch common issues, etc. It might be a good idea to have some form of a mentoring program. This would allow frequent contributors to help potential new contributors know how the project's particular development process works. &lt;/p&gt;

&lt;h2&gt;
  
  
  What the future brings
&lt;/h2&gt;

&lt;p&gt;While this has certainly been an issue for a while, I believe that the recent surge in AI adoption means it's a problem that should be considered sooner rather than later. While open source is certainly far from dead, stagnation due to a surge of contributions isn't very healthy. I hope it's something the community as a whole starts seriously looking into.&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>A Quick Look At The Proc Filesystem</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Thu, 23 Apr 2026 02:17:03 +0000</pubDate>
      <link>https://forem.com/cwprogram/a-quick-look-at-the-proc-filesystem-24g8</link>
      <guid>https://forem.com/cwprogram/a-quick-look-at-the-proc-filesystem-24g8</guid>
      <description>&lt;p&gt;When looking through the filesystem of a Linux system you may notice a directory named &lt;code&gt;/proc&lt;/code&gt;. It's a fascinating directory which exposes many of the internal data for the kernel. I'd like to show some of the interesting information you can get from &lt;code&gt;/proc&lt;/code&gt; as well as some practical applications in popular software. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finding One's Self
&lt;/h2&gt;

&lt;p&gt;One of the more interesting pieces of information you can find is for the current process located in &lt;code&gt;/proc/self&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;cwprogram@rpi:/proc $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lah&lt;/span&gt; /proc/self/
&lt;span class="go"&gt;total 0
dr-xr-xr-x   9 cwprogram cwprogram 0 Apr 22 20:49 .
dr-xr-xr-x 217 root      root      0 Dec 31  1969 ..
dr-xr-xr-x   2 cwprogram cwprogram 0 Apr 22 20:49 attr
-rw-r--r--   1 cwprogram cwprogram 0 Apr 22 20:49 autogroup
-r--------   1 cwprogram cwprogram 0 Apr 22 20:49 auxv
-r--r--r--   1 cwprogram cwprogram 0 Apr 22 20:49 cgroup
--w-------   1 cwprogram cwprogram 0 Apr 22 20:49 clear_refs
-r--r--r--   1 cwprogram cwprogram 0 Apr 22 20:49 cmdline
-rw-r--r--   1 cwprogram cwprogram 0 Apr 22 20:49 comm
-rw-r--r--   1 cwprogram cwprogram 0 Apr 22 20:49 coredump_filter
-r--r--r--   1 cwprogram cwprogram 0 Apr 22 20:49 cpuset
&lt;/span&gt;&lt;span class="gp"&gt;lrwxrwxrwx   1 cwprogram cwprogram 0 Apr 22 20:49 cwd -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/proc
&lt;span class="go"&gt;-r--------   1 cwprogram cwprogram 0 Apr 22 20:49 environ
&lt;/span&gt;&lt;span class="gp"&gt;lrwxrwxrwx   1 cwprogram cwprogram 0 Apr 22 20:49 exe -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/ls
&lt;span class="gp"&gt;&amp;lt;truncate&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to &lt;code&gt;ls&lt;/code&gt; being the current process when the listing is run information for it is made available. There's also a &lt;code&gt;cwd&lt;/code&gt; which points to the current working directory and &lt;code&gt;exe&lt;/code&gt; that points to the executable. There's a &lt;code&gt;status&lt;/code&gt; file available with a decent amount of information as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;cwprogram@rpi:/proc $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;self/status
&lt;span class="go"&gt;Name:   cat
Umask:  0002
State:  R (running)
Tgid:   9755
Ngid:   0
Pid:    9755
PPid:   9687
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use this with a bit of basic grep to get names of processes like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;cwprogram@rpi:/proc $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Name:"&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/status
&lt;span class="go"&gt;102/status:Name:        kworker/0:1H-kblockd
1124/status:Name:       agetty
1126/status:Name:       agetty
11/status:Name: kworker/u16:0-ipv6_addrconf
131/status:Name:        kworker/R-mmc_complete
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;truncate&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part of the path here is the PID itself. So this gives you a somewhat rudimentary process listing. Granted it's certainly not as user friendly as the &lt;code&gt;ps&lt;/code&gt; command. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finding Mounts
&lt;/h2&gt;

&lt;p&gt;Processes also have mount information available in either a &lt;code&gt;mountinfo&lt;/code&gt; or &lt;code&gt;mounts&lt;/code&gt; file. The former is a bit &lt;a href="https://man7.org/linux/man-pages/man5/proc_pid_mountinfo.5.html" rel="noopener noreferrer"&gt;more detailed&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;mountinfo
&lt;span class="go"&gt;20 25 0:19 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw
21 25 0:20 / /proc rw,relatime shared:11 - proc proc rw
22 25 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=1672900k,nr_inodes=418225,mode=755
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the later may be a more familiar output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;cwprogram@rpi:/proc/self $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;mounts
&lt;span class="go"&gt;sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,relatime 0 0
udev /dev devtmpfs rw,nosuid,relatime,size=1672900k,nr_inodes=418225,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=600,ptmxmode=000 0 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is for the process itself keeping namespace restrictions into consideration. &lt;/p&gt;

&lt;h2&gt;
  
  
  Topping It Off
&lt;/h2&gt;

&lt;p&gt;Outside of the standard process information, &lt;code&gt;/proc&lt;/code&gt; also has a number of toplevel files with interesting entries in them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;devices&lt;/code&gt; - Listing of character and block devices&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;meminfo&lt;/code&gt; - Memory statistics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mounts&lt;/code&gt; - Similar to the process version, except for the top system level&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;crypto&lt;/code&gt; - Various crypto ciphers available to the system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stat&lt;/code&gt; - Several statistics about the system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt; - Kernel version string &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmdline&lt;/code&gt; - The options given to the kernel at boot&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filesystems&lt;/code&gt; - Filesystems available to the kernel&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cgroups&lt;/code&gt; - Cgroup information, particularly of use to container based solutions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of these contain useful information for the case of debugging fairly stripped down environments commonly found in container operating systems. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Programmatic Approach
&lt;/h2&gt;

&lt;p&gt;Now this isn't just for operating system debugging. It also has practical uses in modern day software. Take for example kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;            &lt;span class="n"&gt;cmdline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/proc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"cmdline"&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;klog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Infof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error reading file %s: %+v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/proc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"cmdline"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&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://github.com/kubernetes/kubernetes/blob/b31119d205a839aab40b2d819a58d4fabacd9b47/pkg/util/procfs/procfs_linux.go" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this particular case kubernetes is using the proc system to obtain PIDs which match a specific regex. It does so by getting the command name from &lt;code&gt;cmdline&lt;/code&gt; on the process directory level. AWS's firecracker VM which is used to power some of its services such as Lambda also uses &lt;code&gt;/proc&lt;/code&gt; to obtain cgroup directory info from &lt;code&gt;/proc/mounts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;        &lt;span class="c1"&gt;// search PROC_MOUNTS for cgroup mount points&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;File&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proc_mounts_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;JailerError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;FileOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proc_mounts_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Regex courtesy of Filippo.&lt;/span&gt;
        &lt;span class="c1"&gt;// This will match on each line from /proc/mounts for both v1 and v2 mount points.&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// /proc/mounts cointains lines that look like this:&lt;/span&gt;
        &lt;span class="c1"&gt;// cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0&lt;/span&gt;
        &lt;span class="c1"&gt;// cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="c1"&gt;// This Regex will extract:&lt;/span&gt;
        &lt;span class="c1"&gt;//      * "/sys/fs/cgroup/unified" in the "dir" capture group.&lt;/span&gt;
        &lt;span class="c1"&gt;//      * "2" in the "ver" capture group as the cgroup version taken from "cgroup2"; for v1,&lt;/span&gt;
        &lt;span class="c1"&gt;//        the "ver" capture group will be empty (len = 0).&lt;/span&gt;
        &lt;span class="c1"&gt;//      * "[...],relatime,cpu,cpuacct" in the "options" capture group; this is used for&lt;/span&gt;
        &lt;span class="c1"&gt;//        cgroupv1 to determine what controllers are mounted at the location.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;r"^([a-z2]*)[[:space:]](?P&amp;lt;dir&amp;gt;.*)[[:space:]]cgroup(?P&amp;lt;ver&amp;gt;2?)[[:space:]](?P&amp;lt;options&amp;gt;.*)[[:space:]]0[[:space:]]0$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;JailerError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;RegEx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&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://github.com/firecracker-microvm/firecracker/blob/main/src/jailer/src/cgroup.rs" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cython, which is the official C implementation of the Python programming language also uses &lt;code&gt;/proc&lt;/code&gt; for a few things, one of them includes obtaining the parent process ID of a process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;snprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stat_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stat_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"/proc/%d/stat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stat file is a pretty cryptic thing to look at which gives somewhat more parser friendly statistics for a process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9687 (bash) S 9686 9687 9687 34817 9994 4194304 49314 139925 0 3 104 36 299 142 20 0 1 0 43945508 9326592 1490 18446744073709551615 367249391616 367250706224 549003478784 0 0 0 65536 3686404 1266761467 1 0 0 17 3 0 0 0 0 0 367250815728 367250867132 367940014080 549003480647 549003480653 549003480653 549003481070 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/python/cpython/blob/79321fdce3227cf09bb8a2894d856753f1ba098e/Modules/_remote_debugging/subprocess.c" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first entry is the process ID, the second the command, the third entry is the process state, and the fourth entry is the parent process ID of the process. While using C for tokenized parsing such as this is a bit awkward it does get the job done. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping It Up
&lt;/h2&gt;

&lt;p&gt;This is just a small peak into the usefulness that is the proc filesystem. As mentioned it's great when you need a source of information for debugging a Linux based system. It's also useful for handling certain task programmatically, especially if you're doing any form of container development. I urge you to look around &lt;code&gt;/proc&lt;/code&gt; some more to see what other useful things you can find.  &lt;/p&gt;

</description>
      <category>linux</category>
      <category>programming</category>
    </item>
    <item>
      <title>Security and String Interpolation</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Thu, 16 Apr 2026 23:41:25 +0000</pubDate>
      <link>https://forem.com/cwprogram/security-and-string-interpolation-175</link>
      <guid>https://forem.com/cwprogram/security-and-string-interpolation-175</guid>
      <description>&lt;p&gt;I've come back to share knowledge again after being in a long hiatus. Though it's more of a "back to the job searching" situation at the moment. I've recently been looking over recent CVEs and thought I'd share some knowledge of a rather common source of vulnerabilities: string interpolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is String Interpolation?
&lt;/h2&gt;

&lt;p&gt;This is a common feature of most programming languages which is a string containing special values which are then interpreted by the appropriate runtime. Here's a simple example in python:&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;input_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Variable expanded to: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;input_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would output &lt;code&gt;Variable expanded to: Hello, World&lt;/code&gt; when run (well unless you're running a fairly old version of python). These are referred to as &lt;a href="https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals" rel="noopener noreferrer"&gt;formatted string literals or f-strings&lt;/a&gt;. In this case the &lt;code&gt;{input_string}&lt;/code&gt; is a placeholder which is taken by the python runtime and expanded out to the "Hello, World" string. Now a more practical use of this is taking user content and interacting with systems in a dynamic way. Unfortunately, not every user has good intentions. &lt;/p&gt;

&lt;h2&gt;
  
  
  String Interpolation Security
&lt;/h2&gt;

&lt;p&gt;Given how widely used string interpolation is there's several categories of them you can find. The differences tend to be what the overall impact of a successful exploitation is. In most cases these exploits can be prevented by using context specific functions to cleanup any potential malicious content. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Infamous eval
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;eval&lt;/code&gt; in many languages is a function that will evaluate the contents of a string (though certain languages may accept other objects). The main security issue with it is there's no real context and it's unrestricted for the most part. Some languages may offer a more filtered version of &lt;code&gt;eval&lt;/code&gt; such as python's &lt;a href="https://docs.python.org/3/library/ast.html#ast.literal_eval" rel="noopener noreferrer"&gt;ast.literal_eval()&lt;/a&gt;. In general though it's best to avoid &lt;code&gt;eval&lt;/code&gt; and use context specific methods for dealing with user input data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-48868" rel="noopener noreferrer"&gt;CVE-2025-48868&lt;/a&gt; is an example of &lt;code&gt;eval&lt;/code&gt; being used insecurely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell Parsing
&lt;/h3&gt;

&lt;p&gt;This is a specific security concern for programs which deal with running commands based on dynamic user input variables. An example would be an app that uses &lt;code&gt;ffmpeg&lt;/code&gt; to produce thumbnails or re-encode videos. Many of these issue arise if the command is run in a shell environment such as &lt;code&gt;/bin/bash&lt;/code&gt; which supports several programming language primitives. In python for example &lt;code&gt;popen&lt;/code&gt; can accept a &lt;code&gt;shell=True&lt;/code&gt; argument to run it as such.&lt;/p&gt;

&lt;p&gt;This can bit tricky to protect against depending on context. It is possible to spawn the command as a simple process to avoid dealing with certain shell primitives being replaced or acting with unintended consequences. That said, you also have to consider if running programs where the arguments are commands to run. Examples of this are &lt;code&gt;sh -c&lt;/code&gt; and &lt;code&gt;python -c&lt;/code&gt;. You can also associate the process with unprivileged users for most process related functions as well as run the process in a restricted environment. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-40030" rel="noopener noreferrer"&gt;CVE-2026-40030&lt;/a&gt; is a good example of this being exploited.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross Site Scripting (XSS)
&lt;/h3&gt;

&lt;p&gt;This is exploiting string interpolation of the browser. What happens is you have a part of a website, such as a forum post, where users are able to submit their own content. Without any kind of filtering, a user could inject malicious javascript which could redirect a victim to a malware site or view information on the page. Mozilla's developer site has a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS#defenses_against_xss" rel="noopener noreferrer"&gt;security article&lt;/a&gt; on defending against these sort of attacks which I recommend looking into.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-29184" rel="noopener noreferrer"&gt;CVE-2024-29184&lt;/a&gt; is an example of an XSS exploit that even shows how you can bypass one of the defenses against it.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Injection
&lt;/h3&gt;

&lt;p&gt;This is a specific exploit against how databases parse queries. It's common enough that there's even an &lt;a href="https://xkcd.com/327/" rel="noopener noreferrer"&gt;xkcd comic on the topic&lt;/a&gt;. This relies on closing out the intended SQL statement early, adding a malicious statement, and then using a comment to ignore the rest and prevent errors from bailing out the call. As an example from the comic:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Robert'); DROP TABLE Students;--&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Robert');&lt;/code&gt; is assuming the query looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT email FROM Students WHERE name='$name_here');&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So in this case you would get:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT email FROM Students WHERE name='Robert'); DROP TABLE Students;--');&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Which effectively turns it into a multi statement query which drops the table entirely. the &lt;code&gt;--&lt;/code&gt; is a comment which tells the SQL parser to ignore the rest of the string. While there are other methods of defense &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#defense-option-1-prepared-statements-with-parameterized-queries" rel="noopener noreferrer"&gt;prepared statements with parameterized queries&lt;/a&gt; is a popular method of defense. That's because the SQL itself and the input variables are separated out. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/cve-2025-1094" rel="noopener noreferrer"&gt;CVE-2025-1094&lt;/a&gt; is an example of a SQL injection, though targeting a CLI tool instead of the standard web app exploits you would generally find. &lt;/p&gt;

&lt;h3&gt;
  
  
  Path Traversal
&lt;/h3&gt;

&lt;p&gt;This is a security issue where code reading string provided directory paths don't filter or restrict properly leaving the ability to access files that aren't intended. Let's say that there's an API endpoint that lets you download a file. In the request you put &lt;code&gt;../../../etc/passwd&lt;/code&gt;. A server which doesn't filter paths would deliver the &lt;code&gt;/etc/passwd&lt;/code&gt; file to the user assuming the relative path lead to it. Obtaining the proper amount of directory traversal can be trial and error, though the accuracy can be improved by other means such as &lt;a href="https://beaglesecurity.com/blog/vulnerability/information-leakage-of-the-web-applications-directory-or-folder-path.html" rel="noopener noreferrer"&gt;information leakage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many programming languages work to avoid this by having specific functions which set a path anchor and anything above that will fail. &lt;a href="https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.relative_to" rel="noopener noreferrer"&gt;pathlib.PurePath.relative_to&lt;/a&gt; in python is also an example of this. Some 3rd party firewalls such as AWS WAF can sometimes &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html" rel="noopener noreferrer"&gt;have rules to block such attacks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-68428" rel="noopener noreferrer"&gt;CVE-2025-68428&lt;/a&gt; is an example of exploiting path traversal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;If there's one major point to get out of this I would say that in general it's best to not trust user input data. You want to ensure that your risk mitigations assume a malicious actor could abuse user input to attempt an exploit. When you see a string based on user input, consider if there are methods in the language of choice which have context around how you wish to utilize the user data in question. You also want to make sure to have defense in depth. So putting a web application firewall in front of your web servers can help avoid disaster if insecure code manages to slip through. You will want to keep track of what it blocked and insure you're protecting against it on the code side however. &lt;/p&gt;

</description>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Road To Kubernetes: How Older Technologies Add Up</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Mon, 05 Feb 2024 17:16:03 +0000</pubDate>
      <link>https://forem.com/cwprogram/the-road-to-kubernetes-how-older-technologies-add-up-oil</link>
      <guid>https://forem.com/cwprogram/the-road-to-kubernetes-how-older-technologies-add-up-oil</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Virtualization&lt;/li&gt;
&lt;li&gt;Resource Isolation&lt;/li&gt;
&lt;li&gt;Resource Control&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Docker Joins The Stage&lt;/li&gt;
&lt;li&gt;Docker Growth&lt;/li&gt;
&lt;li&gt;Introducing Kubernetes&lt;/li&gt;
&lt;li&gt;Kubernetes And The Cloud&lt;/li&gt;
&lt;li&gt;Component Separation&lt;/li&gt;
&lt;li&gt;The Future?&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;At some point in a DevOps engineer's career they will hear about Kubernetes. It's not a matter of if, but when. With containers being the new hot thing and Kubernetes orchestrating their deployment it's easy to see the reason for its popularity. Even so, Kubernetes is an accumulation of previous technologies. In this article I'd like to discuss the previous way of handling similar application hosting solutions and show how they lead up to a lot of what Kubernetes offers today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtualization
&lt;/h2&gt;

&lt;p&gt;Before containers were popular, virtualization was the way you could split up a single server into multiple "containers". At first they relied on &lt;a href="https://en.wikipedia.org/wiki/Paravirtualization"&gt;paravirtualization&lt;/a&gt; which is what the first EC2 instances used. Virtual Machines would interface to the operating system via some form of middle man which would translate the guest OS's calls to what the host OS would expect. Given the requirement of such a conversion these virtual machines wouldn't be able to keep up with running software straight on the hardware itself. While this wasn't a big issue for some solutions it did mean that adopting it widely where applications cared about performance was a difficult endeavour. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Hardware-assisted_virtualization"&gt;Hardware virtualization&lt;/a&gt; would start to gain traction through the usage of various CPU related technologies specifically to improve performance. Technologies such as &lt;a href="https://linux-kvm.org/page/Main_Page"&gt;KVM&lt;/a&gt; were made to further improve integration with this new technology. However, there were still issues with some hardware interaction that still made it not match up to bare metal. Another alternative was to simply use the same hardware, but isolate the application itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Isolation
&lt;/h2&gt;

&lt;p&gt;Resource isolation primarily started off with isolating how a process could operate. The most popular example of this was &lt;a href="https://en.wikipedia.org/wiki/Chroot"&gt;chroot&lt;/a&gt; (1979). More specialized examples of this included &lt;a href="https://en.wikipedia.org/wiki/FreeBSD_jail"&gt;FreeBSD jails&lt;/a&gt; (2000) and &lt;a href="https://en.wikipedia.org/wiki/Solaris_Containers"&gt;Solaris Zones&lt;/a&gt; (2005) which still kept some of the virtualization aspects. Now chroot, which stands for change root, allowed a process to have their root mount point changed effectively isolating it on a filesystem level. &lt;/p&gt;

&lt;p&gt;For these applications to properly run on a Linux system they need access to special paths which provide system information through a filesystem interface. This includes &lt;a href="https://man7.org/linux/man-pages/man5/proc.5.html"&gt;procfs&lt;/a&gt;, &lt;a href="https://man7.org/linux/man-pages/man4/pts.4.html"&gt;devpts&lt;/a&gt;, and &lt;a href="https://man7.org/linux/man-pages/man5/sysfs.5.html"&gt;sysfs&lt;/a&gt;. These allowed for different mount options for the chroot than the host system they were running on. More simple variants could be done with &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/global_file_system_2/s1-manage-pathnames"&gt;bind mounts&lt;/a&gt;. The underlying application also needed access to its dynamically linked libraries to run. Even static binaries still need access to a c library implementation (ex. glibc) and system calls making setup more difficult if you wanted best performance.&lt;/p&gt;

&lt;p&gt;From a security standpoint this setup was beneficial for webservers who only needed access to their respective HTML. CSS, JS, and any dynamic content generation files. If a basic malicious actor obtained access to a local shell, they would only be able to see files inside the chroot. That's not to say that chroot's are some kind of &lt;a href="https://en.wikipedia.org/wiki/Chroot#Limitations"&gt;unbreakable fortress&lt;/a&gt;. Another issue was that there wasn't a very easy packaging solution which would replicate the ease of use of a virtual machine image. This complexity only increase as more applications are added to a server. Out of the box chroots also don't provide any kind of resource quota mechanism as you would expect from modern day containers. &lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Control
&lt;/h2&gt;

&lt;p&gt;Several technologies were introduced to the Linux kernel to help address some of these issues. &lt;a href="https://man7.org/linux/man-pages/man7/namespaces.7.html"&gt;namespaces&lt;/a&gt; were introduced into the Linux kernel in 2002 for more fine grained isolation. They allowed for scoping resource availability on a process level. You could have one process see one set of resources, and another process see a different set of resources. Next is quotas on resources which has handled by &lt;a href="https://man7.org/linux/man-pages/man7/cgroups.7.html"&gt;cgroups&lt;/a&gt;, released in 2007. They provide a special filesystem mount to configure settings such as what CPUs a process can utilize, how much memory/cpu it can use, and how much network bandwidth it can use. The final set of controls are capabilities, introduced in 1999. While a foundation for process isolation its original purpose was for restrictions on privileged user accounts. Despite its early introductio, the full set of capabilities available today is something that was an accumulation of additions over several kernel releases. While these are powerful technologies, they are also fairly complicated to manage in a scalable manner. They also had the same issue with chroots in that there wasn't a very easy way to package everything together. &lt;/p&gt;

&lt;h2&gt;
  
  
  Containers
&lt;/h2&gt;

&lt;p&gt;While Docker helped drive container adoption, it was &lt;a href="https://linuxcontainers.org/lxc/introduction/"&gt;LXC&lt;/a&gt; which provided the base foundation to make the system work. In fact early versions of Docker utilized it as a backend. It provided the packaging of kernel level features such as cgroups and namespaces which were used for container like isolation. Despite it's features LXC still lacked the modern day interface you'd expect to easily deploy containers. There was also a lack of major cloud managed service providers to help with overall adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Joins The Stage
&lt;/h2&gt;

&lt;p&gt;The initial release of &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; was on March 20th 2013. Important to note is that containers are a more of a concept, and Docker is an implementation. What Docker brought to the table was a container image hosting service as well as software to orchestrate the more difficult parts of the container setup process. Docker also provided a very simplistic image definition: the Dockerfile. Compared to what needed to be done for virtual machine style builds Dockerfile builds simplified the process and used an incremental diff patching feature which enabled more modular organization of images. Images hosted on Docker's platform could be utilized in this process allowing for vendor solution tie-in. Having a business backing it also helped with corporate adoption of containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Growth
&lt;/h2&gt;

&lt;p&gt;Despite this great set of features Docker still would take some years to become the popular solution it was today. This was attributed to the security and scalability concerns of the original iteration. In 2014 &lt;a href="https://docs.docker.com/compose/intro/history/"&gt;Docker Compose&lt;/a&gt; was released as a solution to turning Docker containers into a more "project" like layout. This further helped organization of containers along with the incremental diff functionality. This was further improved via the introduction of &lt;a href="https://docs.docker.com/engine/swarm/"&gt;Docker Swarm&lt;/a&gt; which was released as part of Docker 1.12 in 2016. This allowed a more scalable cluster setup for Docker containers. &lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Kubernetes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; was first released on September 9, 2014. This release timeline is part of what helped it gain a foothold over Docker Swarm. It was an open source version of an internal Google project. Features of container orchestration were presented in a more modular fashion along with scaling functionality. You can chose how your networking stack works, your load balancing, container runtime, and filesystem interfaces. Availability of an API allowed for more programmatic interactions with orchestration, making it tie in very well with CI/CD solutions. However, the big issue it has is complexity of setup. Putting together a Kubernetes cluster with basic functionality is certainly no easy feat. &lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes And The Cloud
&lt;/h2&gt;

&lt;p&gt;Due to the amount of effort required not to just setup, but also migrate to Kubernetes, original enterprise adaptors would rely on the Google Cloud Kubernetes Engine to abstract the effort. The product was released shortly after Kubernetes itself, with the &lt;a href="https://cloudplatform.googleblog.com/2014/11/unleashing-containers-and-kubernetes-with-google-compute-engine.html"&gt;first release&lt;/a&gt; on November 4th 2014. Unfortunately, due to Google Cloud's 2 year gap with AWS's public release this still made Kubernetes adoption on a wider scale more difficult. AWS' first step into the container space was interestingly enough &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/document_history.html"&gt;Elastic Container Service&lt;/a&gt; or ECS in 2015. While not bound with Kubernetes specifically the ease of deployment made it an interesting competitor for more simplistic container workloads. Google's best alternative to it, Google Cloud Run would not see an available release &lt;a href="https://cloud.google.com/run/docs/release-notes"&gt;until 2018&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AWS would then release &lt;a href="https://aws.amazon.com/blogs/aws/amazon-eks-now-generally-available/"&gt;Elastic Kubernetes Services&lt;/a&gt; or EKS in 2018. Azure tagged on slightly afterwards with their own &lt;a href="https://azure.microsoft.com/en-us/blog/azure-kubernetes-service-aks-ga-new-regions-new-features-new-productivity/"&gt;Azure Kubernetes Service&lt;/a&gt; or AKS in the same month. With the 3 major cloud providers all having a managed service Kubernetes started to see a large spike in adoption over the years. So much that a majority of DevOps job descriptions now have it as a technology stack component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component Separation
&lt;/h2&gt;

&lt;p&gt;Kubernetes on the backend used to utilize docker for much of its container runtime solutions. One of the modular features of Kubernetes is the ability to utilize a &lt;a href="https://kubernetes.io/docs/concepts/architecture/cri/"&gt;Container Runtime Interface&lt;/a&gt; or CRI. The problem was that Docker didn't really meet the spec properly and they had to maintain a shim to translate properly. Instead users could utilize the popular &lt;a href="https://containerd.io/"&gt;containerd&lt;/a&gt; or &lt;a href="https://cri-o.io/"&gt;cri-o&lt;/a&gt; runtimes. These follow the &lt;a href="https://opencontainers.org/"&gt;Open Container Initiative&lt;/a&gt; or OCI's guidelines on container formats.&lt;/p&gt;

&lt;p&gt;Projects central to the container experience began to join the &lt;a href="https://www.cncf.io/"&gt;Cloud Native Computing Foundation&lt;/a&gt;. While it supports many popular open source DevOps related &lt;a href="https://www.cncf.io/projects/"&gt;projects&lt;/a&gt;, much of the &lt;a href="https://www.cncf.io/people/governing-board/"&gt;governing board&lt;/a&gt; and &lt;a href="https://www.cncf.io/people/technical-oversight-committee/"&gt;technical oversight committee are corporate oriented&lt;/a&gt;. Kubernetes itself also did a &lt;a href="https://kubernetes.io/blog/2023/08/15/pkgs-k8s-io-introduction/"&gt;migration of their download site&lt;/a&gt; from Google to a more open one which would allow for adding additional mirrors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future?
&lt;/h2&gt;

&lt;p&gt;While Kubernetes is in what I consider to be its prime now, like all technologies there's certainly room for a new tech to take its place. Especially true given the complexity of it even when run in a managed environment. A bump in the right direction could certainly be made by having managed services with specific configurations for specific use cases to be available. Centralization of monitoring/security/performance data along with basic administration would also help it substantially. &lt;/p&gt;

&lt;p&gt;If you like what you see here I'm currently available for remote full time work. Check my profile for links.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>containers</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Understanding CVE Reports</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Sat, 06 Jan 2024 02:18:15 +0000</pubDate>
      <link>https://forem.com/cwprogram/understanding-cve-reports-1337</link>
      <guid>https://forem.com/cwprogram/understanding-cve-reports-1337</guid>
      <description>&lt;ul&gt;
&lt;li&gt;What Is A CVE&lt;/li&gt;
&lt;li&gt;CVE Format&lt;/li&gt;
&lt;li&gt;
CVE Analysis

&lt;ul&gt;
&lt;li&gt;Is The Affected Software Present&lt;/li&gt;
&lt;li&gt;Severity&lt;/li&gt;
&lt;li&gt;Exploit Vector&lt;/li&gt;
&lt;li&gt;Fix Version&lt;/li&gt;
&lt;li&gt;Embargo&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Having A Reporting Program&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;CVEs (Common Vulnerabilities and Exposures) are an official collection of recognized security issues. Knowing these is valuable not just for security, but for other teams such as DevOps and development. A major problem is that some people don't know how to handle a CVE properly. This can lead to unnecessary panic and a loss of valuable company time. In this article I'll be going over what a CVE is and how to analyze one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is A CVE
&lt;/h2&gt;

&lt;p&gt;The concept of CVE was &lt;a href="https://www.cve.org/About/History"&gt;created back in 1999&lt;/a&gt; as a way to centralize vulnerability details. While there is a central governing organization, &lt;a href="https://www.mitre.org/"&gt;MITRE&lt;/a&gt;, actual vulnerability reporting is often handled by other parties. These are known as &lt;a href="https://www.cve.org/ProgramOrganization/CNAs"&gt;CVE Number Authorities&lt;/a&gt; or CNAs. CNAs span a number of scopes which indicate what CVEs they can report against. For example, Adobe is authorized to publish a CVE for one of it's products, but would not be able to publish a CVE for the Python programming language. Some notable CNA examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adobe&lt;/li&gt;
&lt;li&gt;Apple&lt;/li&gt;
&lt;li&gt;Python Foundation&lt;/li&gt;
&lt;li&gt;Oracle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also root CNAs which handle organization of specific scopes of CNAs. Google is one example of a root CNA which covers organization of CVEs related to non Android and Chrome Google products.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE Format
&lt;/h2&gt;

&lt;p&gt;CVEs are currently published based off a &lt;a href="https://www.cve.org/AllResources/CveServices#cve-json-5"&gt;JSON format specification&lt;/a&gt;. Here's an example of an entry for CVE-2023-0038 hosted on the &lt;a href="https://github.com/CVEProject/cvelist/blob/master/2023/0xxx/CVE-2023-0038.json"&gt;CVE Database in GitHub&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MITRE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CVE_data_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CVE-2023-0038"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ASSIGNER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"STATE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eng"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Survey Maker &lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;2013 Best WordPress Survey Plugin&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; plugin for WordPress is vulnerable to Stored Cross-Site Scripting via survey answers in versions up to, and including, 3.1.3 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts when submitting quizzes that will execute whenever a user accesses the submissions page."&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"problemtype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"problemtype_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eng"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"affects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vendor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"vendor_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"vendor_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ays-pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"product_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"product_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Survey Maker &lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;2013 Best WordPress Survey Plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"version_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="nl"&gt;"version_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                            &lt;/span&gt;&lt;span class="nl"&gt;"version_affected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"="&lt;/span&gt;&lt;span class="w"&gt;
                                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reference_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.wordfence.com/threat-intel/vulnerabilities/id/a2a58fab-d4a3-4333-8495-e094ed85bb61"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"refsource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.wordfence.com/threat-intel/vulnerabilities/id/a2a58fab-d4a3-4333-8495-e094ed85bb61"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://plugins.trac.wordpress.org/browser/survey-maker/tags/3.1.4/public/partials/class-survey-maker-submissions-summary-shortcode.php?rev=2839688#L311"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"refsource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://plugins.trac.wordpress.org/browser/survey-maker/tags/3.1.4/public/partials/class-survey-maker-submissions-summary-shortcode.php?rev=2839688#L311"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"credits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chloe Chamberland"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"impact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cvss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"vectorString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"baseScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"baseSeverity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic breakdown is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A description of the CVE&lt;/li&gt;
&lt;li&gt;Type of vulnerability (cross site scripting)&lt;/li&gt;
&lt;li&gt;What is affected&lt;/li&gt;
&lt;li&gt;References (such as vendor postings and bug reports)&lt;/li&gt;
&lt;li&gt;Credit for report&lt;/li&gt;
&lt;li&gt;Overall impact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are sufficient data points to have a basic idea of if one is affected by a vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE Analysis
&lt;/h2&gt;

&lt;p&gt;Since we have the information at hand on what a vulnerability is about it's time to analyze it for potential impact. One thing to keep in mind is that each CVE has a set of circumstances to be considered vulnerable. That means specific setups may not be impacted by it, and that upgrades should be handled in an informed manner to reduce unnecessary upgrades which bring unnecessary risk with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is The Affected Software Present
&lt;/h3&gt;

&lt;p&gt;First off is seeing if the software in question is part of your infrastructure. Server software such as nginx are generally easier to tell. In corporate environments there may be a vulnerability scanning solution in place (or something such as &lt;a href="https://github.blog/2023-01-12-a-smarter-quieter-dependabot/"&gt;dependabot&lt;/a&gt;). There could also be some kind of software inventory system in place as well. If you're in a corporate environment and don't have a system in place, I highly recommend looking into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Severity
&lt;/h3&gt;

&lt;p&gt;Next to consider is the severity, which is an accumulated score system known as CVSS (Common Vulnerability Scoring System). Calculation of this is based on a number of properties of the exploit, some including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is a proof of concept exploit available?&lt;/li&gt;
&lt;li&gt;What level of permissions are needed for the exploit?&lt;/li&gt;
&lt;li&gt;Is an official fix available?&lt;/li&gt;
&lt;li&gt;Is network access required?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and so forth. NIST (National Institute of Standards and Technology) has a &lt;a href="https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator"&gt;calculator with each of the properties explained&lt;/a&gt;. The resulting score will indicate how severe the exploit is. For example on the previous wordpress plugin CVE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"impact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cvss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"vectorString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"baseScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"baseSeverity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attack vector is network based, complexity is low, no permissions are required, and there is no user interaction required. This puts it at a higher severity due to this. Even so the resulting impact is fairly low to critical components such as system availability and integrity. Most security teams will want high and above vulnerabilities handled in a very short time frame. Lower severity indicates the ability to pull off the exploit might not be feasible or apply to your particular configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exploit Vector
&lt;/h3&gt;

&lt;p&gt;Now that severity has been analyzed the next step is to look into how this particular vulnerability is exploited. This is important in cases of highly configurable software such as application servers where features can be enabled/disabled at will. Lets take for example &lt;a href="https://github.com/CVEProject/cvelist/blob/master/2022/41xxx/CVE-2022-41741.json"&gt;CVE-2022-41741&lt;/a&gt;'s description text:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NGINX Open Source before versions 1.23.2 and 1.22.1, NGINX Open Source Subscription before versions R2 P1 and R1 P1, and NGINX Plus before versions R27 P1 and R26 P1 have a vulnerability in the module ngx_http_mp4_module that might allow a local attacker to corrupt NGINX worker memory, resulting in its termination or potential other impact using a specially crafted audio or video file. The issue affects only NGINX products that are built with the ngx_http_mp4_module, when the mp4 directive is used in the configuration file. Further, the attack is possible only if an attacker can trigger processing of a specially crafted audio or video file with the module ngx_http_mp4_module.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case you'll only be affected if you're using the mp4 module in nginx and the system is allowed to process user input media files. That means if you aren't working with the plugin there is no rush to implement a fix. There also may be environmental aspects to a vulnerability. &lt;a href="https://github.com/CVEProject/cvelist/blob/master/2023/5xxx/CVE-2023-5528.json"&gt;CVE-2023-5528&lt;/a&gt; for example only affects kubernetes nodes running on Windows systems. Ensuring you understand exploit vectors can help avoid unnecessary prioritization that could put business value add tasks on hold.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix Version
&lt;/h3&gt;

&lt;p&gt;This is one of the areas where CVE scanning may show its true colors. Taking a CVE at face value, you might think you simply update to the latest version and then it's done. It's not that simple however. The version you're currently on and the version to upgrade two may contain changes that are not related to the vulnerability at all. This means introducing additional risk into systems in case another non-security bug is present. Due to this it's not uncommon for vendors to implement backports. This is where they take the patch and make it work for a previous version of the package. This helps reduce the introduction of risk into systems. For example, if we look at &lt;a href="https://github.com/CVEProject/cvelist/blob/master/2023/46xxx/CVE-2023-46724.json"&gt;CVE-2023-46724&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"product_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"squid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"version_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"version_affected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"version_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;= 3.3.0.1, &amp;lt; 6.4"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The affected versions are greater than &lt;code&gt;3.3.0.1&lt;/code&gt; and less than &lt;code&gt;6.4&lt;/code&gt;. However, if we look at the &lt;a href="https://access.redhat.com/security/cve/CVE-2023-46724"&gt;RedHat security advisory&lt;/a&gt; you'll notice that the fixed package is "squid:4" or more specifically &lt;code&gt;squid-4.15-7.module+el8.9.0+20975+25f17541.5.x86_64.rpm&lt;/code&gt;. Due to RedHat Linux being targeted to slower moving enterprise customers, it's within their business interest to backport instead of updating to the mentioned version 6.4 in the original report. This is why when analyzing a CVE you need to understand where your source of the package is, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your distribution&lt;/li&gt;
&lt;li&gt;Part of a container or virtual machine image&lt;/li&gt;
&lt;li&gt;Part of a language's package manager&lt;/li&gt;
&lt;li&gt;Manual installation via automation such as AWS Systems Manager or Ansible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to note that you may not always have the luxury of a backport available. In such cases you can attempt to do it yourself, or just deal with the added risk of unrelated changes (going over the changelog of a package is a good idea). &lt;/p&gt;

&lt;h3&gt;
  
  
  Embargo
&lt;/h3&gt;

&lt;p&gt;This is a special case for CVEs which have an extremely large number of affected systems and require coordinated effort to push a release out. OpenSSL is where you'd be likely to see an embargo occur (this happened somewhat recently with &lt;a href="https://github.com/CVEProject/cvelist/blob/master/2022/3xxx/CVE-2022-3786.json"&gt;CVE-2022-3786&lt;/a&gt;). This gives time for vendors to upgrade systems / packages (such as Microsoft for example) and allows companies to ensure proper staffing to deal with the issue. The primary purpose of all of this is to prevent the possibility of exploits out in the wild causing a war room situation. That's not to say an exploit won't show up, but the less the chance the better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation
&lt;/h3&gt;

&lt;p&gt;Now assuming you are affected it's time to take action. I will note that some extremely serious vulnerabilities (such as embargo'ed ones) you may not have the luxury of full validation due to the time sensitive nature of it. How you achieve a package update will depend on your particular setup and the source of your packages. Depending on the impact of updating packages it may require a scheduled maintenance window. Before deployment though it's recommended to do some validation. If the report has a proof of concept it's possible to use this to validate that your upgrade will work. You should only use this to validate if the PoC has no destructive operations such as deleting files or DDoS-ing a server. I recommend doing an upgrade and run of the PoC &lt;strong&gt;ON A NON PRODUCTION DEV/TEST SYSTEM&lt;/strong&gt;. If you're hosting on a cloud provider such as AWS you might need permission ahead of time depending on how the PoC operates.&lt;/p&gt;

&lt;p&gt;You'll also want to do system integrity validation with the new version. This is where you do the upgrade, then run your test suites (or cry because you don't have any) to ensure nothing will break. A broken server can sometimes be seen as worse as a vulnerable one. In some cases you may need to report back to the original bug report where the issue was found. Once everything looks good you can look towards moving it to productions. Blue/Green deployment is another method of helping ensure everything is good by doing a test deployment with validation to ensure critical applications are available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Having A Reporting Program
&lt;/h2&gt;

&lt;p&gt;While I've covered how to analyze CVEs there are cases where software you work on may be the target of a vulnerability. The first step is having a dedicated channel for reporting potential security vulnerabilities. It should be highly monitored and if possible initial response made within the same business day. Always keep in communication with the reporter. Failure to do so mean they could potentially release their vulnerability findings (with a potential exploit) before you have a time to fix it. If you expect that security reports may be somewhat more common it might be a good idea to become a CNA and report CVEs on your own.&lt;/p&gt;

&lt;p&gt;Some organizations even go so far as to provide financial incentive to report security issues through bug bounty programs. The Department Of Homeland Security has a &lt;a href="https://bugcrowd.com/dhs-vdp"&gt;bug bounty program&lt;/a&gt; for example. In some cases you may see amounts anywhere from $10,000 - $20,000 depending on the impact of the security vulnerability. Whether or not an organization needs a bug bounty program very much will depend on how high value of a target it is (a mom and pop shop's online store is probably not a good case for one). &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this article has provided insight on how CVEs are formatted, and what information you can get out of them. Being able to understand the potential impact of a CVE can go a long way in ensuring the relevant fixes are properly prioritized, and it's not putting crucial project work at risk unnecessarily. I highly recommend understanding how CVEs work even if you're not in a security specific role. It can really help if you need to implement the fixes yourself.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Different Levels of Project Documentation</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Fri, 29 Dec 2023 22:46:56 +0000</pubDate>
      <link>https://forem.com/cwprogram/different-levels-of-project-documentation-4coc</link>
      <guid>https://forem.com/cwprogram/different-levels-of-project-documentation-4coc</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Naming Convention Documentation&lt;/li&gt;
&lt;li&gt;Typing Documentation&lt;/li&gt;
&lt;li&gt;Comment Documentation&lt;/li&gt;
&lt;li&gt;Project README&lt;/li&gt;
&lt;li&gt;Internals Documentation&lt;/li&gt;
&lt;li&gt;Process Documentation&lt;/li&gt;
&lt;li&gt;General Usage Documentation&lt;/li&gt;
&lt;li&gt;API Documentation&lt;/li&gt;
&lt;li&gt;Advanced Documentation&lt;/li&gt;
&lt;li&gt;External Documentation&lt;/li&gt;
&lt;li&gt;Hosting Documentation&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Documentation is extremely valuable for both open source and internal company projects. With that in mind the question arises on what exactly needs to be documented. While code docs generated from inline formats (ex. python doc strings) may be what is considered to be documentation for developers, there's more to it than that. In this article I'll be breaking down different kinds of documentation and what gives them value to their target audience. Layout wise I'll start at the lowest level and move upwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming Convention Documentation
&lt;/h2&gt;

&lt;p&gt;First off is the naming of variables and functions which drive the code. A common mistake for newer developers is having code with a lot of inline comments. Much of these verbose comments could be addressed by simply having a naming convention which provides clarity on what the purpose of the function or variable is. As an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isAddressPOBox()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Address.isPOBox()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ec2_boto_client&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first and second example shows how naming can change between context. Meanwhile &lt;code&gt;ec2_boto_client&lt;/code&gt; could be useful if you're dealing with multiple boto clients connecting to the AWS API. Something to note here is that &lt;code&gt;isAddressPOBox()&lt;/code&gt; could technically just be &lt;code&gt;isPOBox&lt;/code&gt; but I find that the verbose version also gives context on the input. I would say that you want to get into the habit of doing this for even personal pet projects. It's also very valuable in team environments to make code easier to read (and potentially faster PRs).&lt;/p&gt;

&lt;h2&gt;
  
  
  Typing Documentation
&lt;/h2&gt;

&lt;p&gt;Typing is useful for giving hints on function input and output. While this is default for statically typed languages, some dynamic languages such as python may support type hinting features for this particular purpose:&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;average_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example I know I need to provide a list of integers as input and will obtain a float as output. You will want to be careful on how you organize custom types. A lack of organization can lead to a lot of back and forth on source code reading. Another nice benefit of typing is that it can be used by many IDEs to enhance linting features by seeing if inputs and outputs match the requested types. This is useful for cases where your project is a dependency library as well as working on team environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment Documentation
&lt;/h2&gt;

&lt;p&gt;Documentation through code comments can be useful in cases where code may rely on complex algorithms or when something is done in an unusual manner. I find most of the code I comment on tends to be related to cryptographic hashing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sign&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;_key_object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# SHA256 was chosen here instead of SHA512 as a compromise between decrypt performance and
# hash security
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example I'm explaining the reasoning why I'm using SHA256 to sign a cert when the more powerful SHA512 hash exists. &lt;code&gt;TODO&lt;/code&gt; comments are a specialized form of comment documentation which allows developers to keep track of code improvements quickly, which can be transitioned to tickets/issues/tasks later on. I will warn on being careful on your ratio of comments to code. At a certain point the actual code becomes difficult to follow and could be a sign that your naming convention/typing might have issues.&lt;/p&gt;

&lt;p&gt;## Application Interface Documentation&lt;/p&gt;

&lt;p&gt;This style of documentation is often what developers consider to be "code documentation". Users may reference it for libraries to figure out what functions need to be called. Developers may use it to understand the code base. Such documentation is often found after a function/method signature to document inputs, outputs, and a basic summary of what the code does:&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;average_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Average a list of numbers

    :param numbers: The list of numbers to average
    :type numbers: list[int]

    :return: The average of the numbers
    :rtype: float
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important to note is that the format of application interface documentation will vary depending on the programming language and documentation solution. If you find that files become too verbose due to application interface documentation it might be a sign of functions trying to do too much or a need for separation refactoring. &lt;/p&gt;

&lt;h2&gt;
  
  
  Project README
&lt;/h2&gt;

&lt;p&gt;I would consider this the starting point of figuring out basic information for a project. This includes items such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why the project was created&lt;/li&gt;
&lt;li&gt;Status of the project&lt;/li&gt;
&lt;li&gt;Project requirements&lt;/li&gt;
&lt;li&gt;How to install the project (or a link to an install guide for more complex projects)&lt;/li&gt;
&lt;li&gt;Basic usage / getting started&lt;/li&gt;
&lt;li&gt;Security policy&lt;/li&gt;
&lt;li&gt;Links to further documentation&lt;/li&gt;
&lt;li&gt;Contact information (filing bug reports, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well laid out README can often be a great way to get project adoption as it brings an assumption that the rest of the project is well documented and users will find information they need easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internals Documentation
&lt;/h2&gt;

&lt;p&gt;This somewhat ties in with application interface documentation. It's general used to supplement such documentation and primarily geared towards developers, power users, and potential contributors to a project. A few examples of internals documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.gitea.com/usage/actions/design"&gt;Gitea Actions Design Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://v2.helm.sh/docs/architecture/"&gt;Kubernetes Helm Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devguide.python.org/"&gt;Python Developer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general this type of documentation tends to be useful at larger scale products with wide user adoption. Understanding internals of a project can also help in finding potential performance optimizations or conditions that might cause bugs to occur. &lt;/p&gt;

&lt;h2&gt;
  
  
  Process Documentation
&lt;/h2&gt;

&lt;p&gt;This is used to describe various processes a project might have in dealing with the codebase. Some examples of this include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to contribute to a project&lt;/li&gt;
&lt;li&gt;Setting up a development environment&lt;/li&gt;
&lt;li&gt;Project code of conduct (in particular enforcement of it)&lt;/li&gt;
&lt;li&gt;Bug reporting&lt;/li&gt;
&lt;li&gt;Security reporting guidelines (different from basic bug reporting as it tends to be done in a more private manner to avoid early exploits in the wild)&lt;/li&gt;
&lt;li&gt;Release process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's essentially how users might interact with a project versus how to use the actual project itself. This category of documentation is primarily oriented towards projects with a high contribution rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  General Usage Documentation
&lt;/h2&gt;

&lt;p&gt;General usage documentation is geared towards end users of the project. This may replace parts of the README documentation if certain usage details require a substantial explanation. General usage documentation often touches on the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloading the project (source code/package/installer)&lt;/li&gt;
&lt;li&gt;Installing the project&lt;/li&gt;
&lt;li&gt;Configuring the project&lt;/li&gt;
&lt;li&gt;Validating the project (generally in the form of a quick start guide)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Application interface documentation may be included here as well if the project in question is primarily used as a dependency. Smaller projects may combine download, installation, and configuration steps. Larger projects may need them broken out to handle environment variations such as operating systems, package managers, service management, database engines, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Documentation
&lt;/h2&gt;

&lt;p&gt;Often used for cases where a project exposes a REST or other type of API service. &lt;a href="https://spec.openapis.org/oas/latest.html"&gt;Open API&lt;/a&gt; is a popular method of documenting such API services. It can also be used along side tools such as &lt;a href="https://swagger.io/tools/swagger-codegen/"&gt;Swagger Codegen&lt;/a&gt; to produce boilerplate code for API interaction / testing purposes. There may also be support files for popular API testing tools such as &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt; or &lt;a href="https://insomnia.rest/"&gt;Insomnia&lt;/a&gt;. This makes it easier at a glance to see what data is coming back from a call so the user knows how to handle parsing the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Documentation
&lt;/h2&gt;

&lt;p&gt;General usage documentation works well for covering the standard user needs for the project. Advanced documentation enhances this by showing more advanced setups which are useful but not what you would expect from an out of the box setup. Such documentation could touch on manual configurations that are normally set to developer recommended settings to cover a majority of use cases. Finally there are cases where subject matter expertise is required such as BGB network integration with Kubernetes networking providers. Such documentation is generally recommended for more active projects where the developer is more aware of project use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  External Documentation
&lt;/h2&gt;

&lt;p&gt;This may also be considered "community documentation". Someone writes a blog post on a project, maybe a key note video is posted, essentially anything that isn't directly hosted on core project infrastructure. It may also point to community resources such such as forums, slack channels, and social media sites. One word of caution is that I recommend avoiding slack exclusive documentation for a project as there is a barrier of entry in accessing the information (not to mention separation of concerns). &lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting Documentation
&lt;/h2&gt;

&lt;p&gt;Once you have all the documentation worked out a place to host it will be necessary. Some documentation generation may have ties in with specific hosting sites. &lt;a href="https://about.readthedocs.com/?ref=readthedocs.com"&gt;Read The Docs'&lt;/a&gt; support for Sphinx and other documentation tools is one example. &lt;a href="https://pages.github.com/"&gt;GitHub pages&lt;/a&gt; can be useful for GitHub hosted projects as it integrates well with GitHub Actions CI/CD deployments.&lt;/p&gt;

&lt;p&gt;If you want to self host and don't mind paying a bit a combination of Route53 (unless you host DNS elsewhere), S3, and CloudFront on AWS can provide a pretty reliable and cost effective site hosting. It's also a great solution if you're working on an internal company project with architecture hosted on AWS. It's also nice in compliance environments since you can implement strict access controls when necessary which can integrate with company SSO and other IAM components.&lt;/p&gt;

&lt;p&gt;Wikis are another solution if your contributors are more comfortable with them. It also helps prevent the issue of "documentation updates caused a big CI run" that can catch some projects off guard. For GitHub users there is a fairly minimal &lt;a href="https://docs.github.com/en/communities/documenting-your-project-with-wikis/about-wikis"&gt;GitHub Wiki feature&lt;/a&gt; for projects that support it. This also works on the enterprise versions if you're doing an internally hosted project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As someone who has been a proponent of documentation for much of my career, I hope this provides some insight on the depth of documentation you can have for a project. I will say that some types of documentation tend to show their true value at certain project growth stages. Trying to do advanced usage documentation is not ideal when your project is starting out and you don't have enough usage information. With that said I recommend looking over each documentation type and evaluating if it would help with project adoption / contributions.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Implementing Quality Checks In Your Git Workflow With Hooks and pre-commit</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Wed, 13 Dec 2023 15:45:40 +0000</pubDate>
      <link>https://forem.com/cwprogram/implementing-quality-checks-in-your-git-workflow-with-hooks-and-pre-commit-4iip</link>
      <guid>https://forem.com/cwprogram/implementing-quality-checks-in-your-git-workflow-with-hooks-and-pre-commit-4iip</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Hooks and Webhooks&lt;/li&gt;
&lt;li&gt;Git Hook Basics&lt;/li&gt;
&lt;li&gt;Practical Git Hook Usage&lt;/li&gt;
&lt;li&gt;Git Hook Automation With pre-commit&lt;/li&gt;
&lt;li&gt;Working With Contributed pre-commit Code&lt;/li&gt;
&lt;li&gt;File Filtering&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Git is a powerful tool to enable developers to organize and share their code. One of the more interesting features is hooks. This allows for execution of scripts at certain points in the git workflow. In this article I'll be showcasing one of the simple git workflow events: pre-commit. I'll also be introducing a tool to help with automation of it, the aptly named &lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;. Note that the code used for this is the final step of my python beginners series and can be &lt;a href="https://github.com/cwgem/my-pdm-project"&gt;found in a code repository&lt;/a&gt; in GitHub. Simply click on the green "Code" button and select "Download ZIP". Then extract it into your preferred directory of choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooks and Webhooks
&lt;/h2&gt;

&lt;p&gt;To clear up any potential confusion, Git hooks and GitHub webhooks are different entities (though GitHub webhooks most likely is powered by git hooks). Git hooks are specific to the git software package. &lt;a href="https://docs.github.com/en/webhooks"&gt;GitHub webhooks&lt;/a&gt; are a feature of the GitHub platform that pushes JSON payloads based on various GitHub related events. The same goes for similar source control service sites such as &lt;a href="https://docs.gitlab.com/ee/user/project/integrations/webhooks.html"&gt;GitLab&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Git Hook Basics
&lt;/h2&gt;

&lt;p&gt;A git hook is essentially a script that is executed at a certain point in the git workflow. The basic rules of hooks are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set as executable&lt;/li&gt;
&lt;li&gt;Strip the extension (with the exception of certain windows extensions such as &lt;code&gt;.exe&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Located in a &lt;code&gt;.git/hooks&lt;/code&gt; directory under the repository parent directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To see how this looks download the code mentioned in the introduction paragraph and make sure there's no &lt;code&gt;.git&lt;/code&gt; directory in it (remove it if there is). Then run &lt;code&gt;git init&lt;/code&gt; to initialize the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git init
$ git branch -m main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command ensures the default branch is main, which is fairly common for new repositories on major code hosting sites. After the git repository has been initialized it's time to look at the contents of &lt;code&gt;.git/hooks&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd .git/hooks
$ ls -1
applypatch-msg.sample
commit-msg.sample
fsmonitor-watchman.sample
post-update.sample
pre-applypatch.sample
pre-commit.sample
pre-merge-commit.sample
prepare-commit-msg.sample
pre-push.sample
pre-rebase.sample
pre-receive.sample
push-to-checkout.sample
update.sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these has examples. To make them work we'll need to make a copy with &lt;code&gt;.sample&lt;/code&gt; removed and ensure it's executable. Let's take a look at the &lt;code&gt;pre-commit.sample&lt;/code&gt; contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# An example hook script to verify what is about to be committed.&lt;/span&gt;
&lt;span class="c"&gt;# Called by "git commit" with no arguments.  The hook should&lt;/span&gt;
&lt;span class="c"&gt;# exit with non-zero status after issuing an appropriate message if&lt;/span&gt;
&lt;span class="c"&gt;# it wants to stop the commit.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# To enable this hook, rename this file to "pre-commit".&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--verify&lt;/span&gt; HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1
&lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;against&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HEAD
&lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="c"&gt;# Initial commit: diff against an empty tree object&lt;/span&gt;
        &lt;span class="nv"&gt;against&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git hash-object &lt;span class="nt"&gt;-t&lt;/span&gt; tree /dev/null&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# If you want to allow non-ASCII filenames set this variable to true.&lt;/span&gt;
&lt;span class="nv"&gt;allownonascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git config &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bool hooks.allownonascii&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Redirect output to stderr.&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;1&amp;gt;&amp;amp;2

&lt;span class="c"&gt;# Cross platform projects tend to avoid non-ASCII filenames; prevent&lt;/span&gt;
&lt;span class="c"&gt;# them from being added to the repository. We exploit the fact that the&lt;/span&gt;
&lt;span class="c"&gt;# printable range starts at the space character and ends with tilde.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$allownonascii&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="c"&gt;# Note that the use of brackets around a tr range is ok here, (it's&lt;/span&gt;
        &lt;span class="c"&gt;# even required, for portability to Solaris 10's /usr/bin/tr), since&lt;/span&gt;
        &lt;span class="c"&gt;# the square bracket bytes happen to fall in the designated range.&lt;/span&gt;
        &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--diff-filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;A &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$against&lt;/span&gt; |
          &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[ -~]\0'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; 0
&lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;\&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;        &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# If there are whitespace errors, print the offending file names and fail.&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;git diff-index &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nv"&gt;$against&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this will do a few basic checks against file names and whitespace issues. As one of the comments mentions &lt;code&gt;To enable this hook, rename this file to "pre-commit"&lt;/code&gt;. We can then just run &lt;code&gt;git commit&lt;/code&gt; afterwards to run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cp pre-commit.sample pre-commit
$ cd ../../
$ git commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point nothing happened as nothing was added. I'll go ahead and add a file with a Japanese name to see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touch テスト
$ git add テスト
$ git commit
$ git commit
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see our hook is working and is preventing the non ASCII named file from being committed. I'll go ahead and cleanup the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git reset テスト
$ rm テスト
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Git Hook Usage
&lt;/h2&gt;

&lt;p&gt;So we've seen a very basic example of how hooks work. Now the more practical example would be to use it to run tests on code before committing. Note that I don't recommend this if your project is in the prototyping phase where you may be doing many commits and may not have tests setup due to the frequency of code architecture changes. In this case the project in question has &lt;a href="https://tox.wiki/en/4.11.4/"&gt;tox&lt;/a&gt; setup which runs various python linting and tests. Before running the examples you'll need to ensure &lt;code&gt;pdm&lt;/code&gt; is installed and running under python 3.11. Instructions for this can be found in my &lt;a href="https://dev.to/cwprogram/beginning-python-project-management-with-pdm-13m0"&gt;pdm tutorial&lt;/a&gt;, or you can install it on your own. Once installed we'll make sure the necessary packages are available for tox to work with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pdm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I'll edit the &lt;code&gt;.git/hooks/pre-commit&lt;/code&gt; file to contain the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
pdm run tox

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"tox checks failed"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when &lt;code&gt;git commit&lt;/code&gt; runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit
lint: install_deps&amp;gt; pdm sync --no-self --group testing --group lint
lint: commands[0]&amp;gt; flake8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tox&lt;/code&gt; is run to initiate all the checks. As is the code should be in working condition so you'll see a commit screen after the tox run. Go ahead and close it out without putting a message to abort the comment. When there's an issue with something (I went ahead and commented out one of the imports):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit
lint: install_deps&amp;gt; pdm sync --no-self --group testing --group lint
lint: commands[0]&amp;gt; flake8
&amp;lt;snip&amp;gt;
  lint: FAIL code 1 (4.34=setup[3.69]+cmd[0.65] seconds)
  test: FAIL code 1 (11.80=setup[10.09]+cmd[1.71] seconds)
  docs: OK (14.44=setup[11.79]+cmd[2.65] seconds)
  evaluation failed :( (30.79 seconds)
tox checks failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A message at the end shows that &lt;code&gt;tox&lt;/code&gt; has failed to run and no commit screen is displayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Hook Automation With pre-commit
&lt;/h2&gt;

&lt;p&gt;Given how useful checking code quality is before committing, there's actually a framework around git hooks called &lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;. It's somewhat like a mini-GitHub Actions that can be used to run various commands during the &lt;code&gt;pre-commit&lt;/code&gt; phase. This is where you normally want most CI/CD like checks. Before continuing you'll want to remove the existing hook or you'll end up having duplicated tool runs. We'll also go ahead and add all the files in the repo so there's something to check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ rm .git/hooks/pre-commit
$ git add .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given how useful &lt;code&gt;pre-commit&lt;/code&gt; is across projects I generally recommend installing via &lt;code&gt;pip install --user&lt;/code&gt;, making it part of a tooling virtual environment, or using &lt;a href="https://github.com/pypa/pipx"&gt;pipx&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install --user pre-commit
$ pipx install pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we'll need to create a YAML configuration file in the root of our repository called &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;. To make things simple you can bootstrap a basic one via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pre-commit sample-config &amp;gt; .pre-commit-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As is the file looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# See https://pre-commit.com for more information&lt;/span&gt;
&lt;span class="c1"&gt;# See https://pre-commit.com/hooks.html for more hooks&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v3.2.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trailing-whitespace&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;end-of-file-fixer&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-added-large-files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This already has some great entries for basic git related checks. Now we'll add a new entry to cover tox checking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# See https://pre-commit.com for more information&lt;/span&gt;
&lt;span class="c1"&gt;# See https://pre-commit.com/hooks.html for more hooks&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v3.2.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trailing-whitespace&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;end-of-file-fixer&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-added-large-files&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox check&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox-validation&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm run tox&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;repo: local&lt;/code&gt; tells pre-commit that what I'm doing is not a managed action by code someone else wrote. It's completely custom code written by me. The code itself will run &lt;code&gt;pdm run tox&lt;/code&gt; in the system environment when it finds python files have been modified. This is one of the more powerful features of &lt;code&gt;pre-commit&lt;/code&gt; over the standard git hook. There's more flexibility on when certain commands get run. You don't need to check tests if only the README file was updated. Now to actually have this integrated with our workflow we'll need to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated file is simply an entry point to the actual &lt;code&gt;pre-commit&lt;/code&gt; program which handles the processing of what we want to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# File generated by pre-commit: https://pre-commit.com&lt;/span&gt;
&lt;span class="c"&gt;# ID: 138fd403232d2ddd5efb44317e38bf03&lt;/span&gt;

&lt;span class="c"&gt;# start templated&lt;/span&gt;
&lt;span class="nv"&gt;INSTALL_PYTHON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/johndoe/.pyenv/versions/py311/bin/python3.11
&lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;hook-impl &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.pre-commit-config.yaml &lt;span class="nt"&gt;--hook-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pre-commit&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# end templated&lt;/span&gt;

&lt;span class="nv"&gt;HERE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ARGS+&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nt"&gt;--hook-dir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HERE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTALL_PYTHON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTALL_PYTHON&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-mpre_commit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;elif &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; pre-commit &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;pre-commit &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'`pre-commit` not found.  Did you forget to activate your virtualenv?'&lt;/span&gt; 1&amp;gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now after this I'll add the pre-commit YAML as there's a sanity check to ensure it's part of the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add .pre-commit-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the end goal is that anyone who downloads the code can install your &lt;code&gt;pre-commit&lt;/code&gt; setup themselves and be able to run the appropriate tests. Now looking at the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Trim Trailing Whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook

Fixing README.md

Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check for added large files..............................................Passed
tox-validation...........................................................Failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So as the file I purposely broke hasn't been fixed the &lt;code&gt;tox-validation&lt;/code&gt; stage has failed. It also found an issue with trailing whitespace in my README.md and even fixed it for me. I'll go ahead and add the updated README and fix the commented out import so things are working again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add README.md
$ vim src/my_pdm_project_cwprogram_test/mymath.py
$ git add src/my_pdm_project_cwprogram_test/mymath.py
$ git commit
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check for added large files..............................................Passed
tox-validation...........................................................Passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that everything has passed I'm given the ability to commit. The output is also much more user friendly and mimics what you'd expect from a CI/CD or test suite run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working With Contributed pre-commit Code
&lt;/h2&gt;

&lt;p&gt;In the configuration file you might have noticed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic functionality of this works similar to a GitHub repo with GitHub Actions code. You can even see the code for &lt;a href="https://github.com/pre-commit/pre-commit-hooks/blob/main/pre_commit_hooks/end_of_file_fixer.py"&gt;end_of_file_fixer&lt;/a&gt; as an 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;fix_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Test for newline at end of file
&lt;/span&gt;    &lt;span class="c1"&gt;# Empty files will throw IOError here
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;file_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;last_character&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_obj&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# last_character will be '' for an empty file
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last_character&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;last_character&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Needs this seek for windows, otherwise IOError
&lt;/span&gt;        &lt;span class="n"&gt;file_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_obj&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="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact there's actually a fairly sizeable amount of these &lt;a href="https://pre-commit.com/hooks.html"&gt;listed on the pre-commit website&lt;/a&gt;. In fact another one is &lt;a href="https://pdm-project.org/latest/usage/advanced/#hooks-for-pre-commit"&gt;provided by the PDM project&lt;/a&gt; to make sure &lt;code&gt;pdm.lock&lt;/code&gt; is up to date. I'll go ahead and add this in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# See https://pre-commit.com for more information&lt;/span&gt;
&lt;span class="c1"&gt;# See https://pre-commit.com/hooks.html for more hooks&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v3.2.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trailing-whitespace&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;end-of-file-fixer&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-added-large-files&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox check&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox-validation&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm run tox&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pdm-project/pdm&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.10.4&lt;/span&gt; &lt;span class="c1"&gt;# a PDM release exposing the hook&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm-lock-check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I'll manually edit one of my dependencies in &lt;code&gt;pyproject.toml&lt;/code&gt; and check the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"numpy&amp;gt;=1.25.1",&lt;/span&gt;
    &lt;span class="err"&gt;"requests&amp;gt;=2.31.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add pyproject.tmol .pre-commit-config.yaml
$ git commit
[INFO] Initializing environment for https://github.com/pdm-project/pdm.
[INFO] Installing environment for https://github.com/pdm-project/pdm.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check for added large files..............................................Passed
tox-validation...........................................................Passed
pdm-lock-check...........................................................Failed
- hook id: pdm-lock-check
- exit code: 1

Lock file hash doesn't match pyproject.toml, packages may be outdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case it noticed that my &lt;code&gt;numpy&lt;/code&gt; dependency has changed. I'll go ahead and revert this and see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ vim pyproject.toml
$ git add pyproject.toml
$ git commit
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check for added large files..............................................Passed
tox-validation...........................................................Passed
pdm-lock-check...........................................................Passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now everything is looking good and I'm allowed to commit again. Trying to check if the pdm lock file was synced would have taken a substantial amount of time via a normal git hook, primarily due to not having context on the &lt;code&gt;pdm&lt;/code&gt; code base. Instead I can use this hook provided by the developers in less than ten minutes to do it instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  File Filtering
&lt;/h2&gt;

&lt;p&gt;To showcase this best we'll want to commit what we have now so there's better control over what files get added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit -m "Initial Commit"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now since &lt;code&gt;tox&lt;/code&gt; can run specific stages we can use this to break out tasks based on what's actually been committed. Considering when we'd want things to run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All files should have the basic large files, trailing whitespace, and end of files check&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;README.md&lt;/code&gt; should have a markdown linter run against it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pyproject.toml&lt;/code&gt; should have a toml linter run against it&lt;/li&gt;
&lt;li&gt;Linting should be run if files in &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;tests&lt;/code&gt; are modified&lt;/li&gt;
&lt;li&gt;Tests should be run if files in &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;tests&lt;/code&gt; are modified&lt;/li&gt;
&lt;li&gt;Documentation building should be run if files in &lt;code&gt;src&lt;/code&gt; (for automodule generation) or &lt;code&gt;docs&lt;/code&gt; are modified&lt;/li&gt;
&lt;li&gt;Everything should run if &lt;code&gt;pyproject.toml&lt;/code&gt; is updated as it controls settings and manages dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let's see what a solution like this would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# See https://pre-commit.com for more information&lt;/span&gt;
&lt;span class="c1"&gt;# See https://pre-commit.com/hooks.html for more hooks&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v3.2.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trailing-whitespace&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;end-of-file-fixer&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-toml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-added-large-files&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox lint&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox-validation&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm run tox -e test,lint&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^src\/.+py$|pyproject.toml|^tests\/.+py$&lt;/span&gt;
        &lt;span class="na"&gt;types_or&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;toml&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox docs&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox-docs&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm run tox -e docs&lt;/span&gt;
        &lt;span class="na"&gt;types_or&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rst&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;toml&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^src\/.+py$|pyproject.toml|^docs\/&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pdm-project/pdm&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.10.4&lt;/span&gt; &lt;span class="c1"&gt;# a PDM release exposing the hook&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm-lock-check&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/jumanjihouse/pre-commit-hooks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;markdownlint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First off we have &lt;code&gt;check-toml&lt;/code&gt; for linting our &lt;code&gt;pyproject.toml&lt;/code&gt; file. &lt;code&gt;markdownlint&lt;/code&gt; is taken from &lt;a href="https://github.com/jumanjihouse/pre-commit-hooks"&gt;jumanjihouse/pre-commit-hooks&lt;/a&gt;. Note that &lt;code&gt;check-*&lt;/code&gt; and the markdown linter generally has code to check if appropriate files were added so there's no need to add filters. Another thing to keep in mind is that it's recommended to pin &lt;code&gt;rev&lt;/code&gt; against a specific revision (usually a tag) versus something like &lt;code&gt;master&lt;/code&gt; or &lt;code&gt;main&lt;/code&gt;. This will reduce "unexpected" surprises due to code changes. Here we see the actual filtering at work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt;   &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox lint&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox-validation&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdm run tox -e test,lint&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^src\/.+py$|pyproject.toml|^tests\/.+py$&lt;/span&gt;
        &lt;span class="na"&gt;types_or&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;toml&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;files: ^src\/.+py$|pyproject.toml|^tests\/.+py$&lt;/code&gt; is a regular expression to show what files we're interested in. In this case it's files under &lt;code&gt;src/&lt;/code&gt; and &lt;code&gt;tests/&lt;/code&gt; as well as &lt;code&gt;pyproject.toml&lt;/code&gt;. &lt;code&gt;types_or&lt;/code&gt; (requires 2.9.0 or later pre-commit) also ensures we're only looking at &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;toml&lt;/code&gt; files. If you're wondering what to put in for &lt;code&gt;types_or&lt;/code&gt; the &lt;code&gt;identify-cli&lt;/code&gt; tool will let you know appropriate values that can be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ identify-cli docs/source/index.rst
["file", "non-executable", "rst", "text"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;file&lt;/code&gt; is the most generic type. You can also be more specific with &lt;code&gt;rst&lt;/code&gt; for example. &lt;code&gt;types&lt;/code&gt; can be used if you want something that works off &lt;code&gt;AND&lt;/code&gt; comparison instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;executable&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run if a file is a python file &lt;strong&gt;and&lt;/strong&gt; executable as well. Now that we've seen how comparisons work, it's time to put this into practice. I'll add our updated pre-commit YAML, modify &lt;code&gt;README.md&lt;/code&gt;, and then run &lt;code&gt;git commit&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ vim README.md #changes here
$ git add .pre-commit-config.yaml README.md
$ git commit
[INFO] Initializing environment for https://github.com/jumanjihouse/pre-commit-hooks.
[INFO] Installing environment for https://github.com/jumanjihouse/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check Toml...........................................(no files to check)Skipped
Check for added large files..............................................Passed
tox-validation.......................................(no files to check)Skipped
tox-docs.............................................(no files to check)Skipped
pdm-lock-check.......................................(no files to check)Skipped
Check markdown files.....................................................Passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given that no &lt;code&gt;toml&lt;/code&gt; or appropriate python files were modified unnecessary checks are skipped according to our setup. The &lt;code&gt;README.md&lt;/code&gt; file does have &lt;code&gt;markdownlint&lt;/code&gt; run against it and has passed the checks. I'll go ahead and commit the file with the message "Updated README.md title". Now it's time to see what happens when we make changes to &lt;code&gt;docs/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ vim docs/source/index.rst
$ git add docs/source/index.rst
$ git commit
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check Toml...........................................(no files to check)Skipped
Check for added large files..............................................Passed
tox-validation.......................................(no files to check)Skipped
tox-docs.................................................................Passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case &lt;code&gt;tox-docs&lt;/code&gt; is run but since no python files are present neither linting nor tests were run. Now I'll revert the docs change and modify one of the tests. This should kick off the linting/tests phase but not do anything with the docs building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git reset docs/source/index.rst
$ git checkout docs/source/index.rst
$ vim tests/test_mymath.py
$ git add tests/test_mymath.py
$ git commit
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check Toml...........................................(no files to check)Skipped
Check for added large files..............................................Passed
tox-validation...........................................................Passed
tox-docs.............................................(no files to check)Skipped
pdm-lock-check.......................................(no files to check)Skipped
Check markdown files.................................(no files to check)Skipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we'll make an update to &lt;code&gt;pyproject.toml&lt;/code&gt; which should run everything (I'll also reset the state of the test file so there are no false positives):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git reset tests/test_mymath.py
$ git checkout tests/test_mymath.py
$ vim pyproject.toml
$ git add .pre-commit-config.yaml pyproject.toml
$ git commit
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check Toml...............................................................Passed
Check for added large files..............................................Passed
tox-validation...........................................................Passed
tox-docs.................................................................Passed
pdm-lock-check...........................................................Passed
Check markdown files.................................(no files to check)Skipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything has been run. I will say that technically it wasn't necessary to run this as I only made a description update and nothing was changed in the actual dependencies or tool configuration. That means nothing was changed that would require linting/tests/documentation updates. Even so, trivial &lt;code&gt;pyproject.toml&lt;/code&gt; changes will generally be very rare after the project is flushed out and you should only really be updating for dependency or tool configuration changes from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; is definitely one of the tools I wish I knew about sooner in my development career. It's a great way to avoid having people frown at your PRs for having 3 different lint fix only commits. Given that it is another roadblock to pushing commits you'll want to make sure your linting passes on the entire project via &lt;code&gt;pre-commit run -a&lt;/code&gt;. Otherwise your coworkers will most definitely find a way to bypass it.&lt;/p&gt;

&lt;p&gt;If you like what you see I'm am also available for hire. Those interested can find out more info in my dev.to profile.&lt;/p&gt;

</description>
      <category>git</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Publishing Python Packages To PyPI With Twine</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Wed, 06 Dec 2023 21:21:06 +0000</pubDate>
      <link>https://forem.com/cwprogram/publishing-python-packages-to-pypi-with-twine-4h5</link>
      <guid>https://forem.com/cwprogram/publishing-python-packages-to-pypi-with-twine-4h5</guid>
      <description>&lt;ul&gt;
&lt;li&gt;PyPI Background&lt;/li&gt;
&lt;li&gt;PyPI Account Creation&lt;/li&gt;
&lt;li&gt;Package Refactor&lt;/li&gt;
&lt;li&gt;Twine Upload&lt;/li&gt;
&lt;li&gt;
Adding Metadata

&lt;ul&gt;
&lt;li&gt;Classifiers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;



&lt;p&gt;In the last installment we looked at using tox to centralize our development tool workflow. We now have tests, n centralized way to check our code for issues, and documentation generation all in a single command. Now that our project is solid enough we need to look into releasing it to the general public. In this article we'll look at twine and using it to upload to the official PyPI servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  PyPI Background
&lt;/h2&gt;

&lt;p&gt;The first step to uploading code is having someplace to upload it to. Python's official package repository is &lt;a href="https://pypi.org/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;. This is similar to NPM if you're familiar with NodeJS. It's a centralized location for both uploading and downloading python packages. Packages here are meant to handle everything from common to complex tasks so you don't have to write it yourself. It does mean, however, that you have to be careful about what you install. Malicious packages has been a known issue to not just PyPI, but many other popular package repositories as well. Packages without tests to show confidence that it works or documentation to show how you use it may cost you more time than you save. Finally, you don't want to be relying on code you expect to evolve over time if it's not longer being maintained. &lt;/p&gt;

&lt;h2&gt;
  
  
  PyPI Account Creation
&lt;/h2&gt;

&lt;p&gt;Before we upload anything we'll need a PyPI account. In this case I actually recommend doing so on their &lt;a href="https://test.pypi.org/" rel="noopener noreferrer"&gt;test instance&lt;/a&gt;. This allows you to test your package uploads and installation without causing impact to the main PyPI servers. The sign in form is simple with the usual name, address, username, password, and captcha verification:&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%2Fhp4jdry0jdaini9ck87r.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%2Fhp4jdry0jdaini9ck87r.png" alt="PyPI Signup Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing the signup take note of the username as it will be required later. There's also an email verification step you'll need to complete to ensure you actually own the email address. After this you'll want to setup two factor authentication or 2FA for short. This means you login with two "factors": something you remember and something you own. The &lt;a href="https://pypi.org/help/#twofa" rel="noopener noreferrer"&gt;PyPI help page&lt;/a&gt; has information on setting this up for one of two methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Software based, such as Google Authenticator&lt;/li&gt;
&lt;li&gt;Hardware based, such as a YubiKey&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While YubiKeys are a bit on the pricey side the ability to authenticate by simply plugging in a device (and maybe taping a part of it) is very easy to do. Software solutions that work off of a smart phone can become an issue if your smart phone is misplaced or stolen. Once this is setup you'll want to go to your &lt;a href="https://test.pypi.org/manage/account/" rel="noopener noreferrer"&gt;manage account page&lt;/a&gt; and there is an API token option towards the bottom:&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%2F4okmogtzyxez354miyre.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%2F4okmogtzyxez354miyre.png" alt="PyPI Account Management API Token Option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now tokens are the preferred way to authenticate against the system as if they are compromised you can simply generate a new one. If you use your account username and password instead and it's compromised getting your account back can be tedious. Clicking on Add API Token will lead to a screen that you can fill out like this:&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%2Ftsnsub8y51yfdhnsz1gw.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%2Ftsnsub8y51yfdhnsz1gw.png" alt="PyPI Token Creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the test instance with your new account "Scope" only has one option so the warning can be ignored. Be sure to copy down the value as it will disappear afterwards and you'll have to regenerate it later if you forget. Now that our authentication details are available it's time to setup the upload process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package Refactor
&lt;/h2&gt;

&lt;p&gt;Now up until now we've been using the name &lt;code&gt;my_pdm_project&lt;/code&gt; to represent our package. Unfortunately the issue with this method is that PyPI allows one unique package name globally. This means if someone were to upload &lt;code&gt;my_pdm_project&lt;/code&gt; you wouldn't be able to upload your package code as-is to PyPI. To work around this we're going to rename the project to &lt;code&gt;my_pdm_project_[pypiusername]&lt;/code&gt; where &lt;code&gt;[pypiusername]&lt;/code&gt; is replaced with the username you chose during the PyPI registration process. In this example I'll use &lt;code&gt;my_pdm_project_cwprogram_test&lt;/code&gt; (be sure to not actually use this but the version with your username instead). Please note that this is only this one specific case for general tutorial usage and you normally wouldn't put your username in the package name like that. So let's go ahead and make the changes. The first thing you'll need to do is go to the &lt;code&gt;src&lt;/code&gt; directory and rename your &lt;code&gt;my_pdm_project&lt;/code&gt; to the new one:&lt;/p&gt;

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

$ cd src
$ mv my_pdm_project my_pdm_project_cwprogram_test


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; needs to be updated to reflect the new package name. Also since we're here we're going to update the version to &lt;code&gt;1.0.0&lt;/code&gt; as this is considered our finalized public release:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;

&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-pdm-project-cwprogram-test"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.0"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Test suites need to be updated to point to the new package location in &lt;code&gt;tests/test_mymath.py&lt;/code&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;my_pdm_project_cwprogram_test.mymath&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;add_numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;average_numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;tox.ini&lt;/code&gt; needs an update so that code coverage is using the proper module name:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;

&lt;span class="nn"&gt;[testenv:test]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;testing&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;pytest&lt;/span&gt; &lt;span class="py"&gt;--cov&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my_pdm_project_cwprogram_test&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sphinx will need updates in package name and version via &lt;code&gt;docs/source/config.py&lt;/code&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;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-pdm-project-cwprogram-test&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;copyright&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2023, Chris White&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Chris White&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;release&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally we'll update the module name for our automatic api documentation in &lt;code&gt;docs/source/mymath.rst&lt;/code&gt;:&lt;/p&gt;

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

Mymath Module Documentation
===========================
.. automodule:: my_pdm_project_cwprogram_test.mymath
    :members:


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To make sure we didn't miss anything:&lt;/p&gt;

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

&amp;gt; pdm run tox
&amp;lt;snip&amp;gt;
  lint: OK (4.59=setup[0.84]+cmd[0.56,3.19] seconds)
  test: OK (3.59=setup[3.05]+cmd[0.55] seconds)
  docs: OK (3.55=setup[2.86]+cmd[0.69] seconds)
  congratulations :) (11.81 seconds)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that everything looks good it's time to begin the upload process (I also hope this is a good lesson on why you should stick with a project name once you've decided on it).&lt;/p&gt;

&lt;h2&gt;
  
  
  Twine Upload
&lt;/h2&gt;

&lt;p&gt;Up until now I've recommended adding tools to our dev group in the PDM project. However &lt;code&gt;twine&lt;/code&gt; is something where you want the authentication centralized and usable by all python environments. This fits the description of something that should be installed with &lt;code&gt;pipx&lt;/code&gt; instead:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;gt; pipx install twine&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now we need to configure authentication for twine to upload packages. For simplicity's sake I will be showing how to do this via a simple text file. Once you have gained more experience as a developer I highly recommend switching to &lt;a href="https://pypi.org/project/keyring/" rel="noopener noreferrer"&gt;keyring&lt;/a&gt; as a more secure method (the sooner the better) so your password isn't in a plain text file (I avoided doing it here as keyring can be overwhelming for new developers). To get started you'll need to create a &lt;code&gt;.pypirc&lt;/code&gt; file in your home directory. For Linux and OSX you should be able to shortcut it as &lt;code&gt;~/.pypirc&lt;/code&gt; for Windows it will be in &lt;code&gt;%HOME%\.pypirc&lt;/code&gt; in command prompt and &lt;code&gt;$env:HOME\.pypirc&lt;/code&gt; in Powershell. Here is how the contents should look:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;

&lt;span class="nn"&gt;[distutils]&lt;/span&gt;
&lt;span class="py"&gt;index-servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;testpypi&lt;/span&gt;

&lt;span class="nn"&gt;[testpypi]&lt;/span&gt;
&lt;span class="py"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;https://test.pypi.org/legacy/&lt;/span&gt;
&lt;span class="py"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;__token__&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;pypi-[rest_of_token]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;__token__&lt;/code&gt; as the username is a special indicator that we're using an API token instead of a regular username and password. &lt;code&gt;password&lt;/code&gt; is the API token value you obtained when generating it, which starts with &lt;code&gt;pypi-&lt;/code&gt;. Now that our authentication is in place it's time to test uploading to PyPI. Note that unlike the other tools &lt;code&gt;twine&lt;/code&gt; should only be run when you actually intend to do a release. This means I don't recommend putting it in &lt;code&gt;tox.ini&lt;/code&gt; and instead running it manually. When you become more experienced this process will end up as part of a Continuous Integration / Continuous Deployment (CI/CD) automation pipeline. So now it's time to upload our package:&lt;/p&gt;

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

&amp;gt; pdm build
&amp;gt; twine upload -r testpypi dist/*


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First we're building a package to upload which goes into the &lt;code&gt;dist/&lt;/code&gt; directory. Then we tell &lt;code&gt;twine&lt;/code&gt; to upload the files in that directory. The &lt;code&gt;-r testpypi&lt;/code&gt; tells &lt;code&gt;twine&lt;/code&gt; to use the &lt;code&gt;testpypi&lt;/code&gt; section we added to &lt;code&gt;.pypirc&lt;/code&gt; earlier so it knows where to upload to. This is important as there are actually several potential candidates for uploading python packages to (private repositories such as AWS CodeArtifact and JFrog Artifactory for example). After the upload is complete you will see something like:&lt;/p&gt;

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

View at:
https://test.pypi.org/project/my-pdm-project-cwprogram-test/1.0.0/


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Going to this page will show you the project page and how to install the package. I'll make a virtual environment real quick to show the installation:&lt;/p&gt;

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

&amp;gt; python3.11 -m venv pip_test_install_pypi
&amp;gt; pip_test_install_pypi/Scripts/activate
&amp;gt; pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ my-pdm-project-cwprogram-test 
Looking in indexes: https://test.pypi.org/simple/, https://pypi.org/simple/
Collecting my-pdm-project-cwprogram-test
  Obtaining dependency information for my-pdm-project-cwprogram-test from https://test-files.pythonhosted.org/packages/de/82/cce488c69f576b1ba270d5efc91381010f4a0bbbdf17fb6837c81adeb47b/my_pdm_project_cwprogram_test-1.0.0-py3-none-any.whl.metadata
  Using cached https://test-files.pythonhosted.org/packages/de/82/cce488c69f576b1ba270d5efc91381010f4a0bbbdf17fb6837c81adeb47b/my_pdm_project_cwprogram_test-1.0.0-py3-none-any.whl.metadata (274 bytes)
Collecting numpy&amp;gt;=1.25.2 (from my-pdm-project-cwprogram-test)
  Obtaining dependency information for numpy&amp;gt;=1.25.2 from https://files.pythonhosted.org/packages/da/3c/3ff05c2855eee52588f489a4e607e4a61699a0742aa03ccf641c77f9eb0a/numpy-1.26.2-cp311-cp311-win_amd64.whl.metadata
&amp;lt;snip&amp;gt;
Successfully installed certifi-2023.11.17 charset-normalizer-3.3.2 idna-3.6 my-pdm-project-cwprogram-test-1.0.0 numpy-1.26.2 requests-2.31.0 urllib3-2.1.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So this installation method differs from the one shown on the project page in that it adds the actual PyPI repository as another package source since we're overriding it. Without this the package wouldn't install as it wouldn't be able to find the proper version of &lt;code&gt;numpy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Metadata
&lt;/h2&gt;

&lt;p&gt;As it is right now the package page is pretty bland because we haven't given enough information to describe our package. So it's time to open up &lt;code&gt;pyproject.toml&lt;/code&gt; to add in a few things:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;

&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-pdm-project-cwprogram-test"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.1"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"A tutorial package for building python projects with PDM"&lt;/span&gt;
&lt;span class="py"&gt;keywords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;["tutorial", "pdm"]&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Chris White", email = "me@nospam.com.com"},&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"numpy&amp;gt;=1.25.2",&lt;/span&gt;
    &lt;span class="err"&gt;"requests&amp;gt;=2.31.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;gt;=3.11"&lt;/span&gt;
&lt;span class="py"&gt;readme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"README.md"&lt;/span&gt;
&lt;span class="py"&gt;license&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;{text = "MIT"}&lt;/span&gt;

&lt;span class="nn"&gt;[project.urls]&lt;/span&gt;
&lt;span class="py"&gt;Homepage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://dev.to/cwprogram"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Also the &lt;code&gt;version&lt;/code&gt; needs to be updated to &lt;code&gt;1.0.1&lt;/code&gt; in &lt;code&gt;docs/source/conf.py&lt;/code&gt;. So we've updated the version and description as basic information. There's also a new keywords field which is useful for when a user is searching for packages. &lt;code&gt;[project.urls]&lt;/code&gt; section was added to give a URL for the project (in this case it's just my dev.to page as there's no real project page). Now there's one more piece of useful information that can be added: classifiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classifiers
&lt;/h3&gt;

&lt;p&gt;PyPI and other packaging formats have the concept of classifiers. There's an exhausting yet comprehensive list &lt;a href="https://pypi.org/classifiers/" rel="noopener noreferrer"&gt;on the pypi site&lt;/a&gt;. You can use it to tell users about what python your using, environmental constraints, integration with popular packages, operating systems, etc. Let's take a look at some of them we'll use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Development Status :: 5 - Production/Stable&lt;/code&gt;: This is considered a stable release with unit tests, linting, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Operating System :: OS Independent&lt;/code&gt;: The package doesn't use graphical interfaces or anything special and as such can run on major operating systems&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Intended Audience :: Developers&lt;/code&gt;: The intended audience is developers who are looking to create their own python packages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;License :: OSI Approved :: MIT License&lt;/code&gt;: Technically the &lt;code&gt;license&lt;/code&gt; field already covers this but we'll let people know it's an MIT license as well&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Programming Language :: Python :: 3.11&lt;/code&gt;: This project supports Python 3.11&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Topic :: Education :: Testing&lt;/code&gt;: This is for educational purposes as a test project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The categories are good enough for our purposes. Once you get an actual project going I encourage you to look into these classifiers along with the keywords to make your project more easily discoverable by end users. Now for our finished product:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;

&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-pdm-project-cwprogram-test"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.1"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"A tutorial package for building python projects with PDM"&lt;/span&gt;
&lt;span class="py"&gt;keywords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;["tutorial", "pdm"]&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Chris White", email = "me@nospam.com"},&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"numpy&amp;gt;=1.25.2",&lt;/span&gt;
    &lt;span class="err"&gt;"requests&amp;gt;=2.31.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;gt;=3.11"&lt;/span&gt;
&lt;span class="py"&gt;readme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"README.md"&lt;/span&gt;
&lt;span class="py"&gt;license&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;{text = "MIT"}&lt;/span&gt;
&lt;span class="py"&gt;classifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
  &lt;span class="err"&gt;"Development&lt;/span&gt; &lt;span class="err"&gt;Status&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;5&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Production/Stable",&lt;/span&gt;
  &lt;span class="err"&gt;"Operating&lt;/span&gt; &lt;span class="err"&gt;System&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;OS&lt;/span&gt; &lt;span class="err"&gt;Independent",&lt;/span&gt;
  &lt;span class="err"&gt;"Intended&lt;/span&gt; &lt;span class="err"&gt;Audience&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;Developers",&lt;/span&gt;
  &lt;span class="err"&gt;"License&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;OSI&lt;/span&gt; &lt;span class="err"&gt;Approved&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;MIT&lt;/span&gt; &lt;span class="err"&gt;License",&lt;/span&gt;
  &lt;span class="err"&gt;"Programming&lt;/span&gt; &lt;span class="err"&gt;Language&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;Python&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;3.11",&lt;/span&gt;
  &lt;span class="err"&gt;"Topic&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;Education&lt;/span&gt; &lt;span class="err"&gt;::&lt;/span&gt; &lt;span class="err"&gt;Testing"&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[project.urls]&lt;/span&gt;
&lt;span class="py"&gt;Homepage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://dev.to/cwprogram"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally re-uploading our new code:&lt;/p&gt;

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

&amp;gt; pdm build
&amp;gt; twine upload -r testpypi dist/*


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now if we look at the newer page you'll notice some differences:&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%2F28z4u20wsf4xoe2rx6x1.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%2F28z4u20wsf4xoe2rx6x1.png" alt="Image showing the site with an updated 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%2F9hpb15wsuvnmoghyimzi.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%2F9hpb15wsuvnmoghyimzi.png" alt="Image showing the site with a homepage added"&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%2Ffvsmf45u4yeuxb1g1ta1.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%2Ffvsmf45u4yeuxb1g1ta1.png" alt="Image showing the site with keywords shown"&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%2F8bm56rdzn4b6swf1bgi6.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%2F8bm56rdzn4b6swf1bgi6.png" alt="Image showing the site with classifiers shown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we have a reasonable description shown, our keywords are present, there's a homepage to point users to, and classifiers are available to give the user more metadata about our project. There are a few other fields and some ways to write fields differently, so I recommend looking over the official documentation on &lt;a href="https://packaging.python.org/en/latest/guides/writing-pyproject-toml/" rel="noopener noreferrer"&gt;writing pyproject.toml&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This ends the beginners python series, and it's been a fun ride. It took about two months of work and I hope it helps beginners out there get a start on setting up python projects in a fairly modern way. Once your more comfortable I recommend looking into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to share your code&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pre-commit.com/" rel="noopener noreferrer"&gt;pre-commit&lt;/a&gt; to automate your tox runs with your git workflow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; for implementing CI/CD to automate your releases&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://about.readthedocs.com/" rel="noopener noreferrer"&gt;readthedocs&lt;/a&gt; for hosting your sphinx documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mastering these I would consider a step into being in an early intermediate level as a software developer. I hope you enjoyed this series and please look forward to more articles in the future! If you like what you see please check my dev.to profile as I'm open for work opportunities.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Automating Your Python Dev Tools With Tox</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Sun, 03 Dec 2023 22:34:09 +0000</pubDate>
      <link>https://forem.com/cwprogram/automating-your-python-dev-tools-with-tox-17hk</link>
      <guid>https://forem.com/cwprogram/automating-your-python-dev-tools-with-tox-17hk</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
Tooling Centralization

&lt;ul&gt;
&lt;li&gt;Tox Configuration&lt;/li&gt;
&lt;li&gt;Tox Organization&lt;/li&gt;
&lt;li&gt;Tox Factors&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;So far the code for our beginner's project has become fairly stable and there are linting and testing tools to give confidence in basic functionality. It's almost at the level of being ready to release to the public. Before we do this, however, we need to fix one underlying issue to future project development: running all our tools manually. In this article we'll be looking at tox to centralize our tooling runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling Centralization
&lt;/h2&gt;

&lt;p&gt;So right now we have the following functionality as part of our python project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flake8&lt;/code&gt;: generalized linting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pylint&lt;/code&gt;: more in depth linting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pytest&lt;/code&gt;: test suite and test coverage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sphinx&lt;/code&gt;: document generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also the order we want to execute them as well. Running each of these manually every time isn't very efficient. To deal with this issue we'll be using the python tool &lt;a href="https://tox.wiki/en/4.11.4/"&gt;tox&lt;/a&gt;. As tox is a development tool we'll need to install it as a traditional dev dependency with &lt;code&gt;pdm&lt;/code&gt;. Along with that, there's actually a python package to make working with tox easier in a pdm project. I'll go ahead and install both:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;gt; pdm add -dG dev tox tox-pdm&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tox Configuration
&lt;/h3&gt;

&lt;p&gt;Now tox handles its configuration via an &lt;code&gt;ini&lt;/code&gt; file called &lt;code&gt;tox.ini&lt;/code&gt;. Let's go ahead and build this up slowly for each of the tools mentioned above. To start out create the &lt;code&gt;tox.ini&lt;/code&gt; file in your project's toplevel directory. Then add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;env_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;py311&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is declaring an environment that will be run under python 3.11. It's used on the backend for setting up a virtual environment. Next we'll setup the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;env_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;py311&lt;/span&gt;

&lt;span class="nn"&gt;[testenv]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;flake8&lt;/span&gt;
    &lt;span class="err"&gt;pylint&lt;/span&gt; &lt;span class="py"&gt;--recursive&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;y .&lt;/span&gt;
    &lt;span class="err"&gt;pytest&lt;/span&gt; &lt;span class="py"&gt;--cov&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my_pdm_project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll get to the docs in a later part as that requires an additional change. As far as the layout goes &lt;code&gt;testenv&lt;/code&gt; is the default settings that tox uses for all environments. In this case the default is to install dev group dependencies and run the listed commands. &lt;code&gt;env_list&lt;/code&gt; is something we'll look at in a bit but lets us diversify what we want to run. The current setting simply indicates that we wish to run the commands using python 3.11, shorthanded to &lt;code&gt;py311&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now before actually running anything tox will create a &lt;code&gt;.tox&lt;/code&gt; folder when executed that acts much like our &lt;code&gt;.venv&lt;/code&gt; folder. So this means we'll need to update &lt;code&gt;flake8&lt;/code&gt; and &lt;code&gt;pylint&lt;/code&gt; to ignore it. Open up &lt;code&gt;.flake8&lt;/code&gt; in your project root directory and change the content to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[flake8]&lt;/span&gt;
&lt;span class="py"&gt;max-line-length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;99&lt;/span&gt;
&lt;span class="py"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;.venv/*&lt;/span&gt;
  &lt;span class="err"&gt;.tox/*&lt;/span&gt;
  &lt;span class="err"&gt;docs/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open up &lt;code&gt;pyproject.toml&lt;/code&gt; to update this section to add the ignore as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pylint.MASTER]&lt;/span&gt;
&lt;span class="py"&gt;ignore-paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[ "^.venv/.*$", "^.tox/.*$", "^docs/*" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I've also added &lt;code&gt;docs/&lt;/code&gt; because sphinx from our last article also uses python which we don't have much control over. Now for the moment of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run tox
py311: commands[0]&amp;gt; flake8
py311: commands[1]&amp;gt; pylint --recursive=y .

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

py311: commands[2]&amp;gt; pytest --cov=my_pdm_project
&amp;lt;snip&amp;gt;
py311: OK (6.58=setup[3.38]+cmd[0.55,2.11,0.55] seconds)
congratulations :) (6.66 seconds)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've focused the output but you can see tox is running all the normal commands for us. All of this is condensed into a single easy to run command!&lt;/p&gt;

&lt;h3&gt;
  
  
  Tox Organization
&lt;/h3&gt;

&lt;p&gt;Now while everything is centralized, there's a few issues that can come up in larger projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sometimes you just want to run only linting or only tests&lt;/li&gt;
&lt;li&gt;Linting doesn't require the package to be installed, which &lt;code&gt;tox&lt;/code&gt; will do every time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately for us there's a way to deal with it. &lt;code&gt;tox&lt;/code&gt; supports the concept of multiple environments. That's why the first item is &lt;code&gt;env_list&lt;/code&gt;. It also supports installation groups that we've defined in &lt;code&gt;pyproject.toml&lt;/code&gt; (in this case the dev group with all of our dev tools). Up until now I've recommended installing everything that's not for the base code to be installed in the dev group for simplicity purposes. Now that we've come further in the project it's time to organize these for making our &lt;code&gt;tox&lt;/code&gt; runs cleaner. Right now everything looks like this (save the versions of packages might be different):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pdm.dev-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"flake8&amp;gt;=6.1.0",&lt;/span&gt;
    &lt;span class="err"&gt;"pylint&amp;gt;=3.0.1",&lt;/span&gt;
    &lt;span class="err"&gt;"pytest&amp;gt;=7.4.2",&lt;/span&gt;
    &lt;span class="err"&gt;"pytest-cov&amp;gt;=4.1.0",&lt;/span&gt;
    &lt;span class="err"&gt;"requests-mock&amp;gt;=1.11.0",&lt;/span&gt;
    &lt;span class="err"&gt;"sphinx&amp;gt;=7.2.6",&lt;/span&gt;
    &lt;span class="err"&gt;"tox&amp;gt;=4.11.4",&lt;/span&gt;
    &lt;span class="err"&gt;"tox-pdm&amp;gt;=0.7.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tox&lt;/code&gt; and &lt;code&gt;tox-pdm&lt;/code&gt; are fine as-is in the dev group. The rest we'll break up into &lt;code&gt;linting&lt;/code&gt;, &lt;code&gt;testing&lt;/code&gt;, and &lt;code&gt;doc&lt;/code&gt;. Simply add each group as &lt;code&gt;group_name = []&lt;/code&gt; with the respective version entries inside. Let's do linting first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pdm.dev-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"pytest&amp;gt;=7.4.2",&lt;/span&gt;
    &lt;span class="err"&gt;"pytest-cov&amp;gt;=4.1.0",&lt;/span&gt;
    &lt;span class="err"&gt;"requests-mock&amp;gt;=1.11.0",&lt;/span&gt;
    &lt;span class="err"&gt;"sphinx&amp;gt;=7.2.6",&lt;/span&gt;
    &lt;span class="err"&gt;"tox&amp;gt;=4.11.4",&lt;/span&gt;
    &lt;span class="err"&gt;"tox-pdm&amp;gt;=0.7.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;lint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"flake8&amp;gt;=6.1.0",&lt;/span&gt;
    &lt;span class="err"&gt;"pylint&amp;gt;=3.0.1",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here &lt;code&gt;pylint&lt;/code&gt; and &lt;code&gt;flake8&lt;/code&gt; are now in a dedicate &lt;code&gt;lint&lt;/code&gt; group. Now we'll do the same for testing and docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pdm.dev-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"tox&amp;gt;=4.11.4",&lt;/span&gt;
    &lt;span class="err"&gt;"tox-pdm&amp;gt;=0.7.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;lint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"flake8&amp;gt;=6.1.0",&lt;/span&gt;
    &lt;span class="err"&gt;"pylint&amp;gt;=3.0.1",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;testing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"pytest&amp;gt;=7.4.2",&lt;/span&gt;
    &lt;span class="err"&gt;"pytest-cov&amp;gt;=4.1.0",&lt;/span&gt;
    &lt;span class="err"&gt;"requests-mock&amp;gt;=1.11.0",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;"sphinx&amp;gt;=7.2.6",&lt;/span&gt;
&lt;span class="err"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As tox references the &lt;code&gt;pdm.lock&lt;/code&gt; file, it will need to have all the groups refreshed that is listed within it. This can be done with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm lock -G:all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in &lt;code&gt;tox.ini&lt;/code&gt; it's time to break everything up. We'll get everything we have currently updated first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;env_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;lint, test&lt;/span&gt;

&lt;span class="nn"&gt;[testenv:lint]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;testing, lint&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;flake8&lt;/span&gt;
  &lt;span class="err"&gt;pylint&lt;/span&gt; &lt;span class="py"&gt;--recursive&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;y .&lt;/span&gt;

&lt;span class="nn"&gt;[testenv:test]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;testing&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;pytest&lt;/span&gt; &lt;span class="py"&gt;--cov&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my_pdm_project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The major difference here is that we have a &lt;code&gt;testenv:lint&lt;/code&gt; and &lt;code&gt;testenv:test&lt;/code&gt;. This is known as a "named environment". It's useful for cases of breaking out specific functionality. &lt;code&gt;env_list&lt;/code&gt; will run in the order provided with &lt;code&gt;lint&lt;/code&gt; first followed by &lt;code&gt;test&lt;/code&gt;. For linting the reason why the &lt;code&gt;testing&lt;/code&gt; packages is required is because &lt;code&gt;pylint&lt;/code&gt; is running against our tests and as such needs to be able to resolve the modules we're using. Let's try this out with a quick run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run tox
lint: install_deps&amp;gt; pdm sync --no-self --group testing --group lint
lint: commands[0]&amp;gt; flake8
lint: commands[1]&amp;gt; pylint --recursive=y .

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

lint: OK ✔ in 6.22 seconds
test: install_deps&amp;gt; pdm sync --no-self --group testing
test: commands[0]&amp;gt; pytest --cov=my_pdm_project
================================================= test session starts =================================================
tests\test_mymath.py .........                                                                                   [100%]

---------- coverage: platform win32, python 3.11.5-final-0 -----------
Name                                                     Stmts   Miss  Cover
----------------------------------------------------------------------------
.tox\test\Lib\site-packages\my_pdm_project\__init__.py       0      0   100%
.tox\test\Lib\site-packages\my_pdm_project\mymath.py        25      0   100%
----------------------------------------------------------------------------
TOTAL                                                       25      0   100%


================================================== 9 passed in 0.17s ==================================================
  lint: OK (6.22=setup[3.17]+cmd[0.55,2.50] seconds)
  test: OK (3.31=setup[2.75]+cmd[0.56] seconds)
  congratulations :) (9.61 seconds)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again I've reduced the output to showcase the important parts. Since everything is broken up into separate parts, we can even choose to run only linting for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run tox -e lint
lint: install_deps&amp;gt; pdm sync --no-self --group testing --group lint
lint: commands[0]&amp;gt; flake8
lint: commands[1]&amp;gt; pylint --recursive=y .

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

  lint: OK (4.53=setup[0.86]+cmd[0.56,3.11] seconds)
  congratulations :) (4.62 seconds)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is extremely useful for working on different parts of the development phase where you've passed linting but are trying to just get the tests to pass. Then once the tests are fixed you can run the whole suite to make sure everything as a whole looks fine. For the documentation generation it's slightly similar except we need to enter the &lt;code&gt;docs&lt;/code&gt; directory first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;env_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;lint, test, docs&lt;/span&gt;

&lt;span class="nn"&gt;[testenv:lint]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;testing, lint&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;flake8&lt;/span&gt;
  &lt;span class="err"&gt;pylint&lt;/span&gt; &lt;span class="py"&gt;--recursive&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;y .&lt;/span&gt;

&lt;span class="nn"&gt;[testenv:test]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;testing&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;pytest&lt;/span&gt; &lt;span class="py"&gt;--cov&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;my_pdm_project&lt;/span&gt;

&lt;span class="nn"&gt;[testenv:docs]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;docs&lt;/span&gt;
&lt;span class="py"&gt;changedir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;docs&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sphinx-build source/ build/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sphinx itself is installed thanks to be part of the &lt;code&gt;docs&lt;/code&gt; group we setup in &lt;code&gt;pyproject.toml&lt;/code&gt;. Running again we can see docs are now being generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs: commands[0] C:\Users\johnsmith\my-pdm-project\docs&amp;gt; sphinx-build source/ build/
Running Sphinx v7.2.6
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
writing output...
building [html]: targets for 2 source files that are out of date
updating environment: 0 added, 0 changed, 0 removed
reading sources...
looking for now-outdated files... none found
preparing documents... done
copying assets... copying static files... done
copying extra files... done
done
writing output... [100%] mymath
generating indices... genindex py-modindex done
writing additional pages... search done
dumping search index in English (code: en)... done
dumping object inventory... done
build succeeded.

The HTML pages are in build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another issue here is that while &lt;code&gt;sphinx&lt;/code&gt; and &lt;code&gt;pytest&lt;/code&gt; need our package installed to work properly, linting is just working against the code itself. This means installation of our package just adds unnecessary time. We can skip the process by using &lt;code&gt;skip_install = true&lt;/code&gt; in our lint section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[testenv:lint]&lt;/span&gt;
&lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;testing, lint&lt;/span&gt;
&lt;span class="py"&gt;skip_install&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="err"&gt;flake8&lt;/span&gt;
  &lt;span class="err"&gt;pylint&lt;/span&gt; &lt;span class="py"&gt;--recursive&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;y .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;tox&lt;/code&gt; again shows it's no longer installing our package for the linting session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run tox
lint: install_deps&amp;gt; pdm sync --no-self --group testing --group lint
lint: commands[0]&amp;gt; flake8
lint: commands[1]&amp;gt; pylint --recursive=y .

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

lint: OK ✔ in 4.59 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now unlike the previous definition where we had &lt;code&gt;py311&lt;/code&gt; there's nothing here indicating which version of python is used. In this case &lt;code&gt;tox&lt;/code&gt; simply uses the one from the current environment (python 3.11 in this case, which is what we created the &lt;code&gt;pdm&lt;/code&gt; virtual environment with). It turns out you can be explicit with python versions by utilizing an interesting feature called factors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tox Factors
&lt;/h3&gt;

&lt;p&gt;This is somewhat of a fancy way of saying "names separated by hyphens", with a twist when it comes to python versions. I'm going to go ahead and make an isolated test case to showcase this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;env_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;py311, py3.12&lt;/span&gt;

&lt;span class="nn"&gt;[testenv]&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;python --version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to go ahead and run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run tox
py311: commands[0]&amp;gt; python --version
Python 3.11.5
py3.12: commands[0]&amp;gt; python --version
Python 3.12.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what's happening is that the format &lt;code&gt;pyMajorMinor&lt;/code&gt; and &lt;code&gt;pyMajor.Minor&lt;/code&gt; are special cases called "default factors" and they map to specific python versions. In this case &lt;code&gt;py311&lt;/code&gt; maps to python 3.11 and &lt;code&gt;py3.12&lt;/code&gt; maps to python 3.12. The tox documentation has the full details on special &lt;a href="https://tox.wiki/en/latest/user_guide.html#test-environments"&gt;python interpreter factors&lt;/a&gt;. I will note that this only works if both python 3.11 and python 3.12 are installed on your system. Multiple python versions is an interesting topic, though might be somewhat more of an intermediate discussion. I've &lt;a href="https://dev.to/cwprogram/python-versions-and-release-cycles-22g7"&gt;written about here&lt;/a&gt; if you're interested. This isn't just for python versions though, you can even get a bit more advanced by testing against multiple environment combinations. Let's say for example you made a webapp that you want to ensure works against database A and database B:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tox]&lt;/span&gt;
&lt;span class="py"&gt;env_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;py311-databaseA, py3.12-databaseB&lt;/span&gt;

&lt;span class="nn"&gt;[testenv]&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
  &lt;span class="s"&gt;python --version&lt;/span&gt;
  &lt;span class="err"&gt;databaseA:&lt;/span&gt; &lt;span class="err"&gt;python&lt;/span&gt; &lt;span class="err"&gt;-c&lt;/span&gt; &lt;span class="err"&gt;'print("uses&lt;/span&gt; &lt;span class="err"&gt;databaseA")'&lt;/span&gt;
  &lt;span class="err"&gt;databaseB:&lt;/span&gt; &lt;span class="err"&gt;python&lt;/span&gt; &lt;span class="err"&gt;-c&lt;/span&gt; &lt;span class="err"&gt;'print("uses&lt;/span&gt; &lt;span class="err"&gt;databaseB")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run tox
py311-databaseA: commands[0]&amp;gt; python --version
Python 3.11.5
py311-databaseA: commands[1]&amp;gt; python -c "print(\"uses databaseA\")"
uses databaseA
py311-databaseA: OK ✔ in 3.47 seconds

py3.12-databaseB: commands[0]&amp;gt; python --version
Python 3.12.0
py3.12-databaseB: commands[1]&amp;gt; python -c "print(\"uses databaseB\")"
uses databaseB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because what's actually happening is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;py311-databaseA = factor py311 + factor databaseA&lt;/li&gt;
&lt;li&gt;py3.12-databaseB = factor py3.12 + factor databaseB
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[testenv]&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
  &lt;span class="s"&gt;python --version&lt;/span&gt;
  &lt;span class="err"&gt;databaseA:&lt;/span&gt; &lt;span class="err"&gt;python&lt;/span&gt; &lt;span class="err"&gt;-c&lt;/span&gt; &lt;span class="err"&gt;'print("uses&lt;/span&gt; &lt;span class="err"&gt;databaseA")'&lt;/span&gt;
  &lt;span class="err"&gt;databaseB:&lt;/span&gt; &lt;span class="err"&gt;python&lt;/span&gt; &lt;span class="err"&gt;-c&lt;/span&gt; &lt;span class="err"&gt;'print("uses&lt;/span&gt; &lt;span class="err"&gt;databaseB")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here we can target specific factors, even if all are not included, to take on specific commands or install specific package dependencies. This makes tox an incredibly powerful tool. Even so our current needs are simple so leaving out the python version factors is fine as we're only testing the version set by &lt;code&gt;pdm&lt;/code&gt;. Once you get farther in your python development career you'll be able to better understand how powerful the more advanced usages are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My original intention was to combine this with uploading your package via twine. Then when I started writing it came to realization that the amount of explanation required would have made it a bit too verbose for my liking. I'd also like to note that I'm currently open for work if you like what you see. To not sound too spammy I'll just say check my dev.to profile for more information. In the next section we'll see the final installment of this series where we upload our python code for others to use.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Python Versions and Release Cycles</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Wed, 29 Nov 2023 15:49:11 +0000</pubDate>
      <link>https://forem.com/cwprogram/python-versions-and-release-cycles-22g7</link>
      <guid>https://forem.com/cwprogram/python-versions-and-release-cycles-22g7</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Python 2 and 3&lt;/li&gt;
&lt;li&gt;Python Version Components&lt;/li&gt;
&lt;li&gt;
Python Release Cycle

&lt;ul&gt;
&lt;li&gt;End Of Life&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Bugfix&lt;/li&gt;
&lt;li&gt;Feature&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Which Version To Use

&lt;ul&gt;
&lt;li&gt;Beginners&lt;/li&gt;
&lt;li&gt;Companies&lt;/li&gt;
&lt;li&gt;Package Maintainers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Supporting Multiple Versions&lt;/li&gt;

&lt;li&gt;Migrating Versions&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;



&lt;p&gt;As with all programming languages, the python has &lt;a href="https://www.python.org/doc/versions/" rel="noopener noreferrer"&gt;multiple versions&lt;/a&gt; and a release cycle to go with it. Understanding this release cycle is crucial as a python developer to reducing tech debt in your stack. This article will look at multiple python versions, release cycles, and making decisions on which version to utilize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python 2 and 3
&lt;/h2&gt;

&lt;p&gt;If you've been in python long enough, you might have heard about the fun that is the python 2 to 3 transition. It was a fairly major revamp of the language which required migrations for a substantial portion of the userbase (myself included). It was so much that they &lt;a href="https://docs.python.org/3/library/2to3.html" rel="noopener noreferrer"&gt;ended up making a tool for it&lt;/a&gt;. This unfortunately left a sour taste for many developers as these kind of migrations can put pressure on developers employed in companies who are already attempting to meet deadlines. Needless to say it that many developers were &lt;a href="https://lwn.net/Articles/843660/" rel="noopener noreferrer"&gt;not thrilled about it&lt;/a&gt;. After many extensions of timelines, python 2 &lt;a href="https://www.python.org/doc/sunset-python-2/" rel="noopener noreferrer"&gt;received its end of life on January 1st 2020&lt;/a&gt; with the python 2.7 series. The reason why I'm mentioning all of this is that it's definitely something python developers consider a not so great outcome and has had an impact on how they consider new version / large feature transitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python Version Components
&lt;/h2&gt;

&lt;p&gt;Python versions include a major, minor, and micro component. It's slightly similar in concept to &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semver&lt;/a&gt;, though the minor version may do something incompatible at times. An example of this is the &lt;a href="https://peps.python.org/pep-0632/" rel="noopener noreferrer"&gt;removal of distutils&lt;/a&gt; in 3.12. While technically not a language change persay, it did mean some build systems had to be revamped to accommodate this change. In general you'll see python referred to in a &lt;code&gt;major.minor&lt;/code&gt; format such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 2.7&lt;/li&gt;
&lt;li&gt;Python 3.9&lt;/li&gt;
&lt;li&gt;Python 3.12&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New releases and security fixes are where you'll tend to find them referred to in full version format such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 2.7.17&lt;/li&gt;
&lt;li&gt;Python 3.9.18&lt;/li&gt;
&lt;li&gt;Python 3.12.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security releases use the full format so you know exactly which version(s) are affected. New releases show it so you can tell how many upgrade paths it would take to reach it. Since the release of python 3 in December of 2008 there hasn't been a new major release. This is due to requiring a substantial amount of incompatible changes. There hasn't been an plans announced for a python 4 at this time according to an &lt;a href="https://www.youtube.com/watch?v=aYbNh3NS7jA" rel="noopener noreferrer"&gt;interview with the language's creator&lt;/a&gt;. Though if there was it might be around reworking how the C API is handled which is a pain point for many.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python Release Cycle
&lt;/h2&gt;

&lt;p&gt;The python dev guide has a &lt;a href="https://devguide.python.org/versions/" rel="noopener noreferrer"&gt;good breakdown&lt;/a&gt; of the release cycle process. Here is a screenshot of the current release cycle:&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%2Fnyo2w5v9jmgtn3un9be8.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%2Fnyo2w5v9jmgtn3un9be8.png" alt="screenshot of the current python release cycle as of November 29th 2023"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown here there are quite a number of end-of-life versions, followed by security, then bugfixes, and finally a feature version. All categories are referenced by their respective &lt;code&gt;major.minor&lt;/code&gt; version. Let's take a look at the categories and what they entail.&lt;/p&gt;

&lt;h3&gt;
  
  
  End Of Life
&lt;/h3&gt;

&lt;p&gt;Not much to say here, it basically means the version is not supported by python developers. No security, bugfixes, or feature additions will be added. Both source and binary version updates will no longer be available. You really want to avoid being on an end of life version, especially in corporate environment with security compliance programs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Security releases are where developers will only add security updates, so there will be no feature updates or non-security related bugfixes. They also won't have binaries available on the official website. You generally want to avoid being on the oldest security release as that means an end-of-life will be in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugfix
&lt;/h3&gt;

&lt;p&gt;These versions include fixes for bugs and security related issues. They also have official binaries available for them on the python website. If you're a new developer going for one of the bugfix versions would be ideal since binaries being available make them easier to install. The latest bugfix version would be ideal unless there's a chance that would require updates to popular packages. Bugfixes at some point will become security releases and lose their binary availability after the last bugfix release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature
&lt;/h3&gt;

&lt;p&gt;This is more of a bleeding edge release. It indicates new features planned for the future. As 3.12 was recently released as bugfix, 3.13 is now the feature release. One planned introduction in particular is an &lt;a href="https://discuss.python.org/t/a-steering-council-notice-about-pep-703-making-the-global-interpreter-lock-optional-in-cpython/30474" rel="noopener noreferrer"&gt;optional build mode that removes the global interpreter lock&lt;/a&gt;. It's generally recommended for beginners to avoid these since it's more of an in progress release where certain tutorials may be outdated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Version To Use
&lt;/h2&gt;

&lt;p&gt;So now the question becomes, which version should be utilized? Much of the answer lies in how it's utilized. One thing I will say is that no one should be using an end-of-life version (okay, maybe that one script from 2010 you wrote to parse movie titles). Let's take a look at some of the user categories and what version they might use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beginners
&lt;/h3&gt;

&lt;p&gt;In general I recommend beginners be on the latest bugfix version. The exception to this is I wouldn't pick up a recent bugfix release if it hasn't been out for at least 4 months (3.12 at time of writing would fall under this). This is because tutorials might need updating, and some popular python packages might need time to migrate to it. It's better to play it safe. The main reason for recommending it is that binaries are widely available which make installing them very easy to pull off. This goes for both Windows and Mac in particular.&lt;/p&gt;

&lt;h3&gt;
  
  
  Companies
&lt;/h3&gt;

&lt;p&gt;Recent startups may be closer to the bugfix versions as they don't have systems to migrate and being on newer versions is more advantageous tech debt wise. Enterprises generally stay within the security releases. Their focus is ensuring all their systems work as intended without unreasonable changes introduced. Sometimes there may be other limiting factors such as versions available through external solutions providers. AWS for example has a range of python versions for using with &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" rel="noopener noreferrer"&gt;AWS lambda&lt;/a&gt;. They would want to be on at least 3.8 minimum given that there's a deprecation warning that went out recently for 3.7. There also might be requirements on what third party pip packages are available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Package Maintainers
&lt;/h3&gt;

&lt;p&gt;In general package maintainers will decide which versions to support based on what their userbase looks like and how many people are motivated/available to maintain said versions. At minimum package maintainers will generally use the oldest security release all the way up to the latest bugfix release (save python 3.12 which required some updates). Packages may require other packages which have their own version constraints and can also restrict which version is supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting Multiple Versions
&lt;/h2&gt;

&lt;p&gt;Package maintainers and other individuals may want to support multiple versions of python. For systems such as Windows and MacOSX you can download binaries of the bugfix versions. Security versions on the other hand don't have binaries officially available. In this particular case Linux is nice for working with multiple versions as packages are either installed from source or end binaries are build from source (especially important for security release where only the source is available). Distribution wise there are a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu has some bugfixes available along with a &lt;a href="https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa" rel="noopener noreferrer"&gt;deadsnakes PPA&lt;/a&gt;. Being non-official the security implications should be considered, but it allows for easily installation of various python versions in a distro friendly way.&lt;/li&gt;
&lt;li&gt;Fedora contains a wide range of python packages available through their built-in package manager. You could setup a quick dev box with &lt;a href="https://docs.fedoraproject.org/en-US/quick-docs/raspberry-pi/" rel="noopener noreferrer"&gt;Fedora on Raspberry Pi&lt;/a&gt; as one solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For OSX there is &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;homebrew&lt;/a&gt; or &lt;a href="https://github.com/pyenv/pyenv" rel="noopener noreferrer"&gt;pyenv&lt;/a&gt; (pyenv is another solution on Linux). As pyenv compiles from source it will require setting up XCode (the Apple IDE) tools to support this which can be pretty bulky. Windows users have &lt;a href="https://chocolatey.org/" rel="noopener noreferrer"&gt;chocolatey&lt;/a&gt; but the issue there is it works off the binaries. That means it won't have the latest security release available since those are source only. &lt;a href="https://docs.conda.io/projects/conda/en/latest/user-guide/install/windows.html" rel="noopener noreferrer"&gt;Conda&lt;/a&gt; is also another solution which can be picked up by Visual Studio Code as available versions of Python making development easier. In the end it might be best to consider using &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;WSL&lt;/a&gt; on Windows for installing a Linux version and using that instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating Versions
&lt;/h2&gt;

&lt;p&gt;For companies I recommend looking into having upgrade path at regular intervals. The more you upgrade the less of a chance you'll be stuck with an EOL version. It also makes future upgrades easier, or at least gives you a buffer to handle issues. Having a CI/CD pipeline via something such as GitHun Actions along with a test suite makes this process even easier. Simply upgrade your pipeline to the new version then see what the test suite results are. If everything looks good you may be able to upgrade fairly easily. In the case of there being issues, you can at least have the gradual low priority tasks to investigate the issues and handle them at a better time. This is especially important in businesses with high priority deadlines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This concludes a look into how to work with Python versions. It's something I was planning to go over in my beginners series until I realized it would be better flushed out on its own article. I will note that future articles will have a different direction to them as I'm currently in job hunt mode. This means that the direction will be more towards selling myself as a technically competent individual instead of the usual "I want to mess around with this interesting tech". This includes potentially doing AWS articles that I've tried to avoid here due to not being open source (the stack at least). If you're interested in hiring me for a remote full time position please look at my &lt;a href="https://dev.to/cwprogram"&gt;dev.to profile&lt;/a&gt; for more information.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Documentation With Docstrings and Sphinx</title>
      <dc:creator>Chris White</dc:creator>
      <pubDate>Fri, 10 Nov 2023 17:30:52 +0000</pubDate>
      <link>https://forem.com/cwprogram/python-documentation-with-docstrings-and-sphinx-3mom</link>
      <guid>https://forem.com/cwprogram/python-documentation-with-docstrings-and-sphinx-3mom</guid>
      <description>&lt;ul&gt;
&lt;li&gt;pylint Prep&lt;/li&gt;
&lt;li&gt;Docstrings&lt;/li&gt;
&lt;li&gt;Documentation Generation With sphinx&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;In the last installment of the series we looked at how to achieve testing in our python projects. Much like testing, documentation can go a long way towards achieving adoption for your project. In this case we're going to be looking at how to generate documentation of your code using docstrings and &lt;a href="https://sphinx-rtd-tutorial.readthedocs.io/" rel="noopener noreferrer"&gt;sphinx&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  pylint Prep
&lt;/h2&gt;

&lt;p&gt;When doing the linting I explicitly disabled checking for docstrings. Now that we'll be using them, it's time to enable that check in the &lt;code&gt;pyproject.toml&lt;/code&gt; file. First however, we'll want to check what the current state of our code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .
************* Module src.my_pdm_project.mymath
src\my_pdm_project\mymath.py:10:4: R1720: Unnecessary "elif" after "raise", remove the leading "el" from "elif" (no-else-raise)
src\my_pdm_project\mymath.py:16:10: W3101: Missing timeout argument for method 'requests.get' can cause your program to hang indefinitely (missing-timeout)
src\my_pdm_project\mymath.py:20:4: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
************* Module tests.test_mymath
tests\test_mymath.py:23:11: C0123: Use isinstance() rather than type() for a typecheck. (unidiomatic-typecheck)
tests\test_mymath.py:24:11: C0123: Use isinstance() rather than type() for a typecheck. (unidiomatic-typecheck)

------------------------------------------------------------------
Your code has been rated at 9.25/10 (previous run: 9.25/10, +0.00)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we see how the general development process works of writing code, checking with linting, and fixing things that come up. So first off is this section:&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;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SUPPORTED_OPERATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty simple, it just wants an &lt;code&gt;if&lt;/code&gt; used instead of &lt;code&gt;elif&lt;/code&gt; due to how exceptions handle breaks of logic flow. I'll go ahead and update it here:&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;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SUPPORTED_OPERATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next is that requests is being used without setting a timeout value. If the server was not responsive then our connection might end up in a stuck state. I'll go ahead and fix that here:&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URI&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;operation_expression&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&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 throw an error if a response is not received within 20 seconds. Next is that the else here is redundant:&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;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll go ahead and update that here:&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;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally in our tests we're using &lt;code&gt;type()&lt;/code&gt; instead of &lt;code&gt;isinstance()&lt;/code&gt; which is cleaner. I'll go ahead and update the tests to do that:&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;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that everything is cleaned up I'll go ahead and run &lt;code&gt;pylint&lt;/code&gt; again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .

-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 9.25/10, +0.75)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to enable the docstring check. I'll go ahead and do this by removing the following portion from &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pylint."MESSAGES CONTROL"]&lt;/span&gt;
&lt;span class="py"&gt;disable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'''&lt;/span&gt;
&lt;span class="err"&gt;missing-module-docstring,&lt;/span&gt;
&lt;span class="err"&gt;missing-class-docstring,&lt;/span&gt;
&lt;span class="err"&gt;missing-function-docstring&lt;/span&gt;
&lt;span class="err"&gt;'''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I'll run &lt;code&gt;pylint&lt;/code&gt; again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .
************* Module src.my_pdm_project.mymath
src\my_pdm_project\mymath.py:1:0: C0114: Missing module docstring (missing-module-docstring)
src\my_pdm_project\mymath.py:9:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:25:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:29:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:33:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:37:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:41:0: C0116: Missing function or method docstring (missing-function-docstring)
************* Module tests.test_mymath
tests\test_mymath.py:1:0: C0114: Missing module docstring (missing-module-docstring)
tests\test_mymath.py:16:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:27:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:32:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:39:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:46:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:52:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:62:0: C0116: Missing function or method docstring (missing-function-docstring)

-------------------------------------------------------------------
Your code has been rated at 7.61/10 (previous run: 10.00/10, -2.39)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So as you can see there's a lot of output. Now let's talk about how to go about fixing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docstrings
&lt;/h2&gt;

&lt;p&gt;Docstrings are done by enclosing text in triple double quotes. As an 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;add_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    This is my function
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the official docstring specification is part of &lt;a href="https://peps.python.org/pep-0257/" rel="noopener noreferrer"&gt;PEP 257&lt;/a&gt;. While it does describe the overall format it's not specific about the format of what you would put in a doc string. In this case I'm going to be utilizing sphinx as the code documentation generator of choice. Now let's look at what our function's doc string will become using the sphinx format:&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;add_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add two numbers together

    :param a: The base integer to use in the add operation
    :type a: int
    :param b: The integer to add to the base integer
    :type b: int

    :return: The sum of both integers
    :rtype: int
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;:param:&lt;/code&gt; indicates what a parameter is meant for and &lt;code&gt;:type:&lt;/code&gt; indicates the type of the parameter. &lt;code&gt;:return:&lt;/code&gt; will describe the return of the function and &lt;code&gt;:rtype:&lt;/code&gt; the return value. Now after implementing this docstring and running &lt;code&gt;pylint&lt;/code&gt; again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .
************* Module src.my_pdm_project.mymath
src\my_pdm_project\mymath.py:1:0: C0114: Missing module docstring (missing-module-docstring)
src\my_pdm_project\mymath.py:9:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:39:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:43:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:47:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:51:0: C0116: Missing function or method docstring (missing-function-docstring)
************* Module tests.test_mymath
tests\test_mymath.py:1:0: C0114: Missing module docstring (missing-module-docstring)
tests\test_mymath.py:16:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:27:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:32:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:39:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:46:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:52:0: C0116: Missing function or method docstring (missing-function-docstring)
tests\test_mymath.py:62:0: C0116: Missing function or method docstring (missing-function-docstring)

------------------------------------------------------------------
Your code has been rated at 7.76/10 (previous run: 7.61/10, +0.15)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see there is a slight increase in our overall score since we added the docstring. Now one issue here is that pylint is expecting the tests to have docstrings. Functionality wise we really don't need them in our tests since the point of code documentation is to show the users how the code they're consuming works. A general user isn't going to be consuming tests. At the top of the test file I can tell &lt;code&gt;pylint&lt;/code&gt; to ignore docstrings for them since I still want it to make sure the other parts of my tests are solid:&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;# pylint: disable=missing-docstring
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote_plus&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests_mock&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;my_pdm_project.mymath&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;add_numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now after running &lt;code&gt;pylint&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .
************* Module src.my_pdm_project.mymath
src\my_pdm_project\mymath.py:1:0: C0114: Missing module docstring (missing-module-docstring)
src\my_pdm_project\mymath.py:9:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:39:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:43:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:47:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:51:0: C0116: Missing function or method docstring (missing-function-docstring)

------------------------------------------------------------------
Your code has been rated at 9.05/10 (previous run: 8.96/10, +0.09)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I'm a big closer here. Now besides the functions it's also mentioning a module docstring. At the top of our python code we can simply write a description of what the underlying code is meant to 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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
A module containing simple math operations.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote_plus&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now another check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .
************* Module my_pdm_project.mymath
src\my_pdm_project\mymath.py:12:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:42:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:46:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:50:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:54:0: C0116: Missing function or method docstring (missing-function-docstring)

------------------------------------------------------------------
Your code has been rated at 9.21/10 (previous run: 9.05/10, +0.16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now just the function docstrings are left. Let's look at a more complex example for &lt;code&gt;make_mathjs_request&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&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;operation&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Make a expression call against the MathJS API

    :param a: Base integer for the operation
    :type a: int
    :param b: Integer to use with a in the operation
    :type b: int
    :param operation: Operation to run against a and b
    :type operation: str

    :raises ZeroDivisionError: Raised if division by 0
    :raises ValueError: Raised if not a supported operation

    :returns:
        - int for non division operations
        - float for division operations
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here it's somewhat like what we saw before. What's new now is that we also documentation exceptions that can be raised, and why they'd be raised. The &lt;code&gt;:returns:&lt;/code&gt; is used since we're returning either a float or int depending on the operation. As mentioned before this was done as an example case for showing testing and normally you wouldn't want a function to return multiple types. After a &lt;code&gt;pylint&lt;/code&gt; run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .
************* Module my_pdm_project.mymath
src\my_pdm_project\mymath.py:58:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:62:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:66:0: C0116: Missing function or method docstring (missing-function-docstring)
src\my_pdm_project\mymath.py:70:0: C0116: Missing function or method docstring (missing-function-docstring)

------------------------------------------------------------------
Your code has been rated at 9.37/10 (previous run: 9.21/10, +0.16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I just need to add docstrings to the other functions. After all is done the file looks like 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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
A module containing simple math operations.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote_plus&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://api.mathjs.org/v4/?expr=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SUPPORTED_OPERATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&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;operation&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Make a expression call against the MathJS API

    :param a: Base integer for the operation
    :type a: int
    :param b: Integer to use with a in the operation
    :type b: int
    :param operation: Operation to run against a and b
    :type operation: str

    :raises ZeroDivisionError: Raised if division by 0
    :raises ValueError: Raised if not a supported operation

    :returns:
        - int for non division operations
        - float for division operations
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SUPPORTED_OPERATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;

    &lt;span class="n"&gt;operation_expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;quote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URI&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;operation_expression&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&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;operation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add two numbers together

    :param a: The base integer to use in the add operation
    :type a: int
    :param b: The integer to add to the base integer
    :type b: int

    :return: The sum of both integers
    :rtype: int
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subtract_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Subtract two numbers

    :param a: The base integer to use in the subtract operation
    :type a: int
    :param b: The integer to subtract the base integer from
    :type b: int

    :return: The subtraction of both numbers
    :rtype: int
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Multiple two numbers together

    :param a: The base integer to use in the multiply operation
    :type a: int
    :param b: The integer to multiply against the base number
    :type b: int

    :return: The result of multiplying both numbers
    :rtype: int
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;divide_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Divide two numbers

    :param a: The base integer to use in the add operation
    :type a: int
    :param b: The integer divide a by
    :type b: int

    :return: The quotient of the division operation
    :rtype: float
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;make_mathjs_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;average_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Average a list of numbers

    :param numbers: The list of numbers to average
    :type numbers: list[int]

    :return: The average of the numbers
    :rtype: float
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After finishing up documenting everything here we can check what &lt;code&gt;pylint&lt;/code&gt; has to say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run pylint --recursive=y .

-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 9.37/10, +0.63)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is in the clear now! &lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation Generation With sphinx
&lt;/h2&gt;

&lt;p&gt;Now as is the code documentation is a bit difficult to work with for an average user. To help with this we can use &lt;a href="https://sphinx-rtd-tutorial.readthedocs.io/" rel="noopener noreferrer"&gt;sphinx&lt;/a&gt; to take our docstrings and generate them into various formats. As with our other tools this will be added as a development package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm add -dG dev sphinx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll need to create a document directory for where our documentation will be stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; mkdir docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to setup sphinx to generate documentation. The thing to keep in mind with sphinx is it's primarily a documentation generation tool and generation of library documentation is a side bonus. With this in mind we'll go ahead and setup how our project will work vi the nice &lt;code&gt;sphinx-quickstart&lt;/code&gt; utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; cd docs
&amp;gt; pdm run sphinx-quickstart --no-makefile -M --ext-autodoc -p "my-pdm-project" -a "Chris White" -v "0.3.0" -r "0.3.0" -l "en" --sep .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So there's a few things to digest here. &lt;code&gt;--no-makefile&lt;/code&gt; and &lt;code&gt;-M&lt;/code&gt; are done to avoid using make for building. This was mostly to avoid adding in another thing to install. &lt;code&gt;-p&lt;/code&gt; sets the name of the project, &lt;code&gt;-a&lt;/code&gt; the author, &lt;code&gt;-v&lt;/code&gt; and &lt;code&gt;-r&lt;/code&gt; are for version and release. They're the same right now because there's no 1.0 release yet, but if there was I'd recommend something like &lt;code&gt;1.0&lt;/code&gt; for the version and &lt;code&gt;1.0.0&lt;/code&gt; for the release. It's somewhat like how there's &lt;code&gt;3.12&lt;/code&gt; for python but the &lt;code&gt;3.12&lt;/code&gt; version has several releases under it. &lt;code&gt;-l&lt;/code&gt; sets the language of the project and &lt;code&gt;--sep&lt;/code&gt; ensures that source and build directories are separate. I tend to prefer this because it's easier to ignore the build directory through things like &lt;code&gt;.gitignore&lt;/code&gt; later on. Finally &lt;code&gt;.&lt;/code&gt; indicates the directory of the project, or more specifically the "documentation project" (as supposed to the code project where all our code is). Now that everything is setup we can use &lt;code&gt;sphinx-build&lt;/code&gt; to generate html for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run sphinx-build source/ build/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if I look in the build directory there will be an &lt;code&gt;index.html&lt;/code&gt; I can access via a browser:&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%2Fwdj8r2at04ase4baxn8c.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%2Fwdj8r2at04ase4baxn8c.png" alt="Sample home page view of sphinx generated HTML documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right now there isn't much going on and "Module Index" doesn't work because it hasn't been setup to recognize our docstrings. To do this we'll create a new file in &lt;code&gt;docs/source/&lt;/code&gt; called &lt;code&gt;mymath.rst&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mymath Module Documentation
===========================
.. automodule:: my_pdm_project.mymath
    :members:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now this format is known as rst or &lt;a href="https://www.sphinx-doc.org/en/master/usage/restructuredtext/" rel="noopener noreferrer"&gt;reStructuredText&lt;/a&gt;. It's a format that's more feature rich than markdown and useful for structured documentation. In this case it's referring to a function in rst. These functions are in the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.. function_name:: arguments
    :option: value

    content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the case of automodule it will generate documentation for &lt;code&gt;my_pdm_project.mymath&lt;/code&gt; including all of its members. Now in the same directory there will be an &lt;code&gt;index.rst&lt;/code&gt; file that needs to be edited like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.. my-pdm-project documentation master file, created by
   sphinx-quickstart on Fri Nov 10 09:01:15 2023.
   You can adapt this file completely to your liking, but it should at least
   contain the root `toctree` directive.

Welcome to my-pdm-project's documentation!
==========================================
.. toctree::
   :maxdepth: 2
   :caption: Contents:

   mymath


Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's changed is that &lt;code&gt;mymath&lt;/code&gt; has now been added to our table of contents. Note that contents are indicated by a blank line after the options. In this case the content is &lt;code&gt;mymath&lt;/code&gt; on a single line. &lt;code&gt;sphinx&lt;/code&gt; knows that this is referencing &lt;code&gt;mymath.rst&lt;/code&gt; and &lt;code&gt;toctree&lt;/code&gt; will automatically parse &lt;code&gt;mymath.rst&lt;/code&gt; to provide a table of contents. Now after running this again in the &lt;code&gt;docs&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; pdm run sphinx-build source/ build/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main &lt;code&gt;index.html&lt;/code&gt; page will show:&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%2Fbypl0z9n0jfujc3n5fwz.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%2Fbypl0z9n0jfujc3n5fwz.png" alt="Image showing a detailed view of the module documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And clicking on "Mymath Module Documentation" will show the documentation generated via our doc strings:&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%2F1k72n04tchuayef43pd5.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%2F1k72n04tchuayef43pd5.png" alt="Image showing the module table of contents on the main index.html page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This concludes a look at documentation generation via sphinx parsing python docstrings. I will say that rst is more involved than simple markdown, but it's feature rich nature makes it ideal for many forms of documentation structure. In the next section we'll be looking at orchestrating all of our tools so far and uploading our code for everyone to use.&lt;/p&gt;

</description>
      <category>python</category>
      <category>documentation</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
