<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Liz Acosta</title>
    <description>The latest articles on DEV Community by Liz Acosta (@lizzzzz).</description>
    <link>https://dev.to/lizzzzz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F520299%2F38de40f4-43b2-4a0d-a7cc-2fa6494ac4b3.png</url>
      <title>DEV Community: Liz Acosta</title>
      <link>https://dev.to/lizzzzz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lizzzzz"/>
    <language>en</language>
    <item>
      <title>Seeking Validation: Data Validation in Python with Pydantic and Vonage Verify</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Thu, 06 Aug 2026 19:59:45 +0000</pubDate>
      <link>https://dev.to/vonagedev/seeking-validation-data-validation-in-python-with-pydantic-and-vonage-verify-33p6</link>
      <guid>https://dev.to/vonagedev/seeking-validation-data-validation-in-python-with-pydantic-and-vonage-verify-33p6</guid>
      <description>&lt;p&gt;&lt;em&gt;This blog post provides an introduction to Pydantic, the popular data validation library for Python. It includes a demonstration of the value Pydantic provides using the Vonage Verify API.&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Duck-typing in Python?
&lt;/h3&gt;

&lt;p&gt;To understand why &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; is such a useful library, it is important to first understand how typing works in Python. In programming, “typing” refers to systems of classifying and categorizing data. How data is typed determines how it can be acted upon – for instance, you can’t add the word “two” with the number 2.&lt;/p&gt;

&lt;p&gt;Python owes a lot of its success and popularity to its flexibility around typing. Unlike most compiled languages that require you to explicitly declare types, Python implements something called &lt;strong&gt;duck-typing&lt;/strong&gt;. Duck-typing is a dynamic type system that assumes that if an object “walks like a duck and quacks like a duck, then it must be a duck.”&lt;/p&gt;

&lt;p&gt;More specifically, &lt;a href="https://docs.python.org/3/glossary.html#term-duck-typing" rel="noopener noreferrer"&gt;according to the Python documentation&lt;/a&gt;, duck-typing is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A programming style which does not look at an object’s type to determine if it has the right interface; instead, the method or attribute is simply called or used … By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using &lt;code&gt;type()&lt;/code&gt; or &lt;code&gt;isinstance()&lt;/code&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, an example of this is Python’s &lt;code&gt;len&lt;/code&gt; function. &lt;code&gt;len()&lt;/code&gt; returns “&lt;a href="https://docs.python.org/3/library/functions.html#len" rel="noopener noreferrer"&gt;the length (the number of items) of an object&lt;/a&gt;.” In the code below, observe how &lt;code&gt;len()&lt;/code&gt; successfully produces an output regardless of the type of object it is called with:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;example_1&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;str&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="gp"&gt;
&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;example_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="gp"&gt;
&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;example_3&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;hello&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;world&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;foo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;dict&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="gp"&gt;
&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;                                 &lt;span class="c1"&gt;# 13 letters in "Hello? World!"
&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;                                  &lt;span class="c1"&gt;# 4 items in the list
&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;                                  &lt;span class="c1"&gt;# 2 key-value pairs in the dictionary
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an example of dynamic typing that more likely represents what you might see in Python in the real world, the code below defines three different classes of dog breeds that can each &lt;code&gt;bark()&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;class&lt;/span&gt; &lt;span class="nc"&gt;Pug&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;bark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The pug goes arf arf!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LabradorRetriever&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;bark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The labrador retriever goes arf arf!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Wolfhound&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;bark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The wolfhound goes arf arf!&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;You can instantiate one of each kind of dog breed and call &lt;code&gt;bark()&lt;/code&gt; on every one of them without error because they all possess the same &lt;em&gt;interface&lt;/em&gt;. Because of duck-typing, the following code runs successfully:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;LabradorRetriever&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Wolfhound&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;pug&lt;/span&gt; &lt;span class="n"&gt;goes&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;labrador&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="n"&gt;goes&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;wolfhound&lt;/span&gt; &lt;span class="n"&gt;goes&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt; &lt;span class="n"&gt;arf&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;pet&lt;/code&gt; can be of class &lt;code&gt;Pug&lt;/code&gt;, &lt;code&gt;LabradorRetriever&lt;/code&gt;, or &lt;code&gt;Wolfhound&lt;/code&gt; and Python will still call the &lt;code&gt;bark&lt;/code&gt; function without error.&lt;/p&gt;

&lt;p&gt;This can be convenient, and in many contexts, it's exactly what you want. For scripting, data science, and exploratory work, Python's dynamic typing is a feature, not a bug. Duck-typing lets you iterate quickly without the overhead of rigid type declarations. This is a big part of why Python has become the dominant language for AI and data science workflows.&lt;/p&gt;

&lt;p&gt;However, the calculus changes significantly in production backend code. A Flask or Django application with no declared types can introduce maintenance and debugging friction. With Python’s duck-typing, type mismatches surface as cryptic runtime errors rather than clear, early failures. The problem becomes even more acute when applications rely on specific types of data in order to run as intended, such as when working with APIs, processing configuration files, or handling user input. &lt;/p&gt;

&lt;p&gt;For instance, if you defined the following class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sphynx&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;meow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The sphynx goes meow meow!&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;And then updated the example code to include an instance of &lt;code&gt;Sphynx&lt;/code&gt; in the list of &lt;code&gt;pets&lt;/code&gt; to iterate through, you will run into an error:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;LabradorRetriever&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Sphynx&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     

&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;pug&lt;/span&gt; &lt;span class="n"&gt;goes&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;labrador&lt;/span&gt; &lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="n"&gt;goes&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt; &lt;span class="n"&gt;arf&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="nc"&gt;Traceback &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;python-input-74&amp;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;line&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;^^^^^^^^&lt;/span&gt;
&lt;span class="nb"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sphynx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bark&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As this example illustrates, within certain contexts, the flexibility of duck-typing becomes a liability. For example, a wrong type passed to an API call might not fail until the request is already in flight, potentially costing you money on a chargeable call that was always destined to fail.&lt;/p&gt;

&lt;p&gt;One way to address some of the issues that arise with dynamic typing is with Python’s &lt;a href="https://docs.python.org/3/library/typing.html" rel="noopener noreferrer"&gt;type hints&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Type Annotations?
&lt;/h2&gt;

&lt;p&gt;A type hint is a code annotation that “&lt;a href="https://docs.python.org/3/glossary.html#term-type-hint" rel="noopener noreferrer"&gt;specifies the expected type for a variable, a class attribute, or a function parameter or return value&lt;/a&gt;.” Therefore, a &lt;strong&gt;type annotation&lt;/strong&gt; refers to how type hints are used syntactically in Python.&lt;/p&gt;

&lt;p&gt;The following code is a basic example of using type annotation:&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;greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the type hint is &lt;code&gt;str&lt;/code&gt; and the type annotation is the &lt;code&gt;name: str&lt;/code&gt; and &lt;code&gt;-&amp;gt; str&lt;/code&gt; syntax. The type annotation tells us that the expected type of the parameter &lt;code&gt;name&lt;/code&gt; is a string and that the expected return type of the function &lt;code&gt;greeting&lt;/code&gt; is a string.&lt;/p&gt;

&lt;p&gt;Adding type annotations to the example &lt;code&gt;Pug&lt;/code&gt; class could look 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bark&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The pug &lt;/span&gt;&lt;span class="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; goes arf arf!&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;And if we update our &lt;code&gt;pets&lt;/code&gt; iteration code to include type annotation, it could look 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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_dogs_bark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Pug&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;LabradorRetriever&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Wolfhound&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pet&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

&lt;span class="nf"&gt;make_dogs_bark&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;LabradorRetriever&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Sphynx&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the name suggests, these are merely type &lt;em&gt;hints&lt;/em&gt;. They help make the code more readable and maintainable, but they are not enforced by the Python interpreter. We could use an additional tool like &lt;a href="https://mypy.readthedocs.io/en/latest/getting_started.html" rel="noopener noreferrer"&gt;mypy&lt;/a&gt; to check the types and receive an error 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="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;incompatible&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sphynx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pug | LabradorRetriever | Wolfhound&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;item&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 have to be performed as an additional step – nothing is preventing this code from executing and generating an error at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeking Validation
&lt;/h2&gt;

&lt;p&gt;To prevent errors at runtime – such as an API call with incorrect data – you could &lt;em&gt;validate&lt;/em&gt; the data in your request &lt;em&gt;before&lt;/em&gt; sending it. In the example code, we could define some sort of validation to ensure each item in the list &lt;code&gt;pets&lt;/code&gt; can &lt;code&gt;bark()&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_dogs_bark_safely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dogs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Manual validation in the function&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dogs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bark&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bark&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&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;Invalid dog type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&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;dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Now this fails immediately with a clear message:
&lt;/span&gt;&lt;span class="n"&gt;pets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Sphynx&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="nf"&gt;make_dogs_bark_safely&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# TypeError: Invalid dog type: Sphynx
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to quickly becoming tedious and unwieldy, this pattern undermines some of the conveniences of Python’s inherent dynamic-typing while also missing out on some of the benefits of type annotation.&lt;/p&gt;

&lt;p&gt;Fortunately, &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; provides data validation while maintaining the ease and speed of the language using syntax already baked in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Pydantic?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pydantic.dev/docs/validation/latest/get-started/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; is a data validation library for Python offering the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Leverages &lt;a href="https://docs.python.org/3/glossary.html#term-type-hint" rel="noopener noreferrer"&gt;type hints&lt;/a&gt; to &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/why#type-hints" rel="noopener noreferrer"&gt;control schema validation and serialization&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/why#performance" rel="noopener noreferrer"&gt;Rust under the hood&lt;/a&gt; for its core validation logic resulting in minimal impact on performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/why#dataclasses-typeddict-more" rel="noopener noreferrer"&gt;validation of many standard library types&lt;/a&gt; including &lt;code&gt;dataclass&lt;/code&gt; and &lt;code&gt;TypedDict&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Works in either &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/why#strict-lax" rel="noopener noreferrer"&gt;strict or lax mode&lt;/a&gt; when handling types.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can output &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; out of the box, which enables seamless integration with other tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically generates documentation from models.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s take a closer look at how these features come together in practice to provide reliable, efficient data validation in harmony with Python syntax and structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Validation Features of Pydantic
&lt;/h3&gt;

&lt;p&gt;It is important to understand that in Pydantic, “validation” refers to “&lt;a href="https://pydantic.dev/docs/validation/latest/concepts/models/" rel="noopener noreferrer"&gt;the process of instantiating a model (or other type) that adheres to specified types and constraints&lt;/a&gt;.” In other words, Pydantic’s validation applies to the output of a model instantiation, not the input data. As such, Pydantic raises a &lt;code&gt;ValidationError&lt;/code&gt; when data cannot be successfully parsed into an instance of a model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BaseModel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BaseModel&lt;/code&gt; lies at the heart of Pydantic. You inherit from it to define data models with type-annotated fields. One of the primary methods to define schema in Pydantic is via models. When you instantiate a &lt;code&gt;BaseModel&lt;/code&gt; Pydantic automatically validates the data against the defined schema.&lt;/p&gt;

&lt;p&gt;Using Pydantic, the &lt;code&gt;Pug&lt;/code&gt; class now 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fawn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;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;The pug &lt;/span&gt;&lt;span class="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; goes arf arf!&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;If you were to instantiate &lt;code&gt;Pug&lt;/code&gt;, Pydantic would make sure the resulting object has a &lt;code&gt;name&lt;/code&gt; of type string, an &lt;code&gt;age&lt;/code&gt; of type integer, and a &lt;code&gt;color&lt;/code&gt; of type string with &lt;code&gt;fawn&lt;/code&gt; as the default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type Hints and Required Fields&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Type annotations control schema validation and serialization. Fields declared with just a type annotation are required; fields with defaults are optional.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;Pug&lt;/code&gt; example, the &lt;code&gt;name&lt;/code&gt; field is annotated with &lt;code&gt;str&lt;/code&gt; meaning any string will suffice, but a string &lt;em&gt;must&lt;/em&gt; be provided; the same applies to the &lt;code&gt;age&lt;/code&gt; field. The &lt;code&gt;color&lt;/code&gt; field, however,  provides a default of &lt;code&gt;fawn&lt;/code&gt; and thus not required when instantiating a &lt;code&gt;Pug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type Coercion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pydantic can be used in either strict or the default lax mode. In lax mode, the library will automatically convert data to the type defined in the schema.&lt;/p&gt;

&lt;p&gt;So, for example, if we supplied &lt;code&gt;"5"&lt;/code&gt; as a string for the &lt;code&gt;age&lt;/code&gt; of a &lt;code&gt;Pug&lt;/code&gt;, Pydantic would convert it to an integer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Field Configuration
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Field&lt;/code&gt; function enables the addition of metadata and constraints for schema fields.&lt;/p&gt;

&lt;p&gt;Some commonly used parameters are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints&lt;/strong&gt; such as greater than &lt;code&gt;gt&lt;/code&gt;) and greater than or equal to &lt;code&gt;ge&lt;/code&gt;), and &lt;code&gt;min_length&lt;/code&gt; and &lt;code&gt;max_length&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Aliases&lt;/strong&gt; for mapping input and output field names using &lt;code&gt;alias&lt;/code&gt;, &lt;code&gt;validation_alias&lt;/code&gt;, and &lt;code&gt;serialization_alias&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strict mode&lt;/strong&gt; to prevent type coercion on certain fields &lt;code&gt;strict=True&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To enforce constraints on the &lt;code&gt;name&lt;/code&gt; field for &lt;code&gt;Pug&lt;/code&gt;, you would use the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The pug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fawn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;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;The pug &lt;/span&gt;&lt;span class="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; goes arf arf!&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;h3&gt;
  
  
  Serialization
&lt;/h3&gt;

&lt;p&gt;Pydantic model instances can be converted to dictionaries without writing any additional serialization code by using &lt;code&gt;model_dump()&lt;/code&gt;. This makes it straightforward to serialize to JSON or other formats to use elsewhere in a project. For example, &lt;code&gt;model_dump_json()&lt;/code&gt; serializes a model directly to JSON.&lt;/p&gt;

&lt;p&gt;Below is an example of creating an instance of &lt;code&gt;Pug&lt;/code&gt; and then serializing it using one of Pydantic’s built-in functions:&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;matty_pug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Matty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matty_pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which would produce the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Matty&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;age&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;color&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fawn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is a Real-World Use Case of Pydantic?
&lt;/h2&gt;

&lt;p&gt;Since its inception, Pydantic has quickly become one of the most widely used data validation libraries for Python. Approximately 8,000 packages on PyPI use Pydantic including (and most famously) FastAPI, huggingface, Django Ninja, SQLModel, LangChain, and Vonage.&lt;/p&gt;

&lt;p&gt;In November 2024, Vonage &lt;a href="https://developer.vonage.com/en/blog/vonage-python-sdk-v4-is-now-live-" rel="noopener noreferrer"&gt;released a full, ground-up rewrite of the Vonage Python SDK&lt;/a&gt;. It’s not always easy to start over completely, but the rewrite was an opportunity to make some critical structural enhancements and improve user interaction with the SDK.&lt;/p&gt;

&lt;p&gt;Among some of the changes included in this initiative was the addition of Pydantic to the SDK in order to facilitate calling Vonage APIs and parsing the responses.&lt;/p&gt;

&lt;p&gt;Check out the video below to hear from one of our developers who works on the Python SDK and see what he has to say about using Pydantic.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the Vonage Python SDK Uses Pydantic Models
&lt;/h3&gt;

&lt;p&gt;Using Pydantic models to form requests enforces correct typing and makes it easier to pass the right objects to the Vonage APIs. With version 4 of the SDK, API responses are now deserialized into fully documented Pydantic models, which provide more consistency than returning dictionaries like in the prior version. Additionally, you can still turn Pydantic models into dictionaries or JSON strings with &lt;code&gt;model.model_dump&lt;/code&gt; and &lt;code&gt;model.model_dump_json&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;For this demonstration, we’ll take a look at the Vonage Verify API and how it is modeled using Pydantic in the SDK.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Vonage Verify API?
&lt;/h3&gt;

&lt;p&gt;The Verify API is Vonage’s next generation two-factor authentication (2FA) product. With Verify API, you can authenticate your users and prevent fraud with a simple, easy-to-use API that abstracts away the complexity of 2FA at a global scale.&lt;/p&gt;

&lt;p&gt;It expands on traditional authentication methods by supporting a wider range of channels, including over-the-top (OTT) channels like WhatsApp, as well as SMS, voice, and email. The API supports both &lt;a href="https://developer.vonage.com/en/blog/using-jwt-for-authentication-in-a-golang-application-dr#jwt-introduction" rel="noopener noreferrer"&gt;JSON web tokens&lt;/a&gt; (JWT) and &lt;a href="https://developer.vonage.com/en/verify/concepts/authentication" rel="noopener noreferrer"&gt;Basic authentication&lt;/a&gt;. Basic authentication is easier to get started with, but does not support advanced features such as ACLs. You can use either JWT or Basic authentication, but not both at the same time. You can read more about authentication in the &lt;a href="https://developer.vonage.com/en/verify/concepts/authentication" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At a high level, the Verify API follows this workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An end-user triggers a 2FA request in an application&lt;/li&gt;
&lt;li&gt;On the backend, this 2FA request initiates a Verify API request &lt;/li&gt;
&lt;li&gt;The Verify API sends a one time password (OTP) to the end-user via the configured channel (SMS, voice, WhatsApp, or email)&lt;/li&gt;
&lt;li&gt;The end-user provides the OTP to the application&lt;/li&gt;
&lt;li&gt;This initiates a Verify request to check the OTP provided by the end-user against the OTP generated by the API&lt;/li&gt;
&lt;li&gt;The result of that check then determines what happens next (the end-user is authenticated, etc)&lt;/li&gt;
&lt;/ol&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%2Fa.storyblok.com%2Ff%2F270183%2F842x440%2F74fbe1e257%2Fverifyv2_request_summary_callbacks.png%2Fm%2Ffit-in" 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%2Fa.storyblok.com%2Ff%2F270183%2F842x440%2F74fbe1e257%2Fverifyv2_request_summary_callbacks.png%2Fm%2Ffit-in" alt="A diagram of the Verify V2 Request with Summary Callbacks." width="842" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A diagram of the Verify V2 Request with Summary Callbacks.&lt;/em&gt;&lt;br&gt;
A typical initially verification request includes the following payload:&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;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es-es"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"channel_timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"client_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myPersonalRef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"code_length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e4dR1Qz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"template_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;"4ed3027d-8762-44a0-aa3f-c393717413a4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&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;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"44770090000"&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;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"44770090000"&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;Which is explained more in-depth below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Required or optional&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;locale&lt;/td&gt;
&lt;td&gt;The locale to use for the verification message&lt;/td&gt;
&lt;td&gt;Optional, defaults to &lt;code&gt;en-us&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;channel_timeout&lt;/td&gt;
&lt;td&gt;The time in seconds to wait between attempts to deliver the verification code&lt;/td&gt;
&lt;td&gt;Optional, defaults to 180 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client_ref&lt;/td&gt;
&lt;td&gt;A unique identifier for the verification request&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;code_length&lt;/td&gt;
&lt;td&gt;The length of the verification code to generate&lt;/td&gt;
&lt;td&gt;Optional, defaults to 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;code&lt;/td&gt;
&lt;td&gt;An optional alphanumeric custom code to use, if you don't want Vonage to generate the code&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brand&lt;/td&gt;
&lt;td&gt;The name of the company or service that is sending the verification request – this will appear in the body of the SMS or TTS message&lt;/td&gt;
&lt;td&gt;Required, maximum length of 16 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;template_id&lt;/td&gt;
&lt;td&gt;A custom template ID to use – works only when &lt;code&gt;channel&lt;/code&gt; is &lt;code&gt;sms&lt;/code&gt; or &lt;code&gt;rcs&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;workflow&lt;/td&gt;
&lt;td&gt;The list of channels to use in the verification workflow, used in the order they are listed&lt;/td&gt;
&lt;td&gt;Required, maximum length of 3 items&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To learn more about the API and how it works, refer to the &lt;a href="https://developer.vonage.com/en/verify/overview?source=verify" rel="noopener noreferrer"&gt;Vonage Verify documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A successful request responds with a &lt;code&gt;202 OK&lt;/code&gt; to indicate that the verification request has been initiated. The response will also include a &lt;code&gt;request_id&lt;/code&gt; which is required to complete the verification process:&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="nl"&gt;"request_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;"c11236f4-00bf-4b89-84ba-88b25df97315"&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;At the same time, Vonage sends an OTP to the end-user via the configured workflow, attempting each channel in the order in which they are defined.&lt;/p&gt;

&lt;p&gt;Once the end-user receives and provides the OTP to the application, another request to the Verify endpoint with the &lt;code&gt;request_id&lt;/code&gt; as a path parameter &lt;code&gt;https://api.nexmo.com/v2/verify/:request_id&lt;/code&gt;) and the OTP in the request body for the &lt;code&gt;code&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;If the code provided matches the code generated and sent by Vonage, a &lt;code&gt;200 OK&lt;/code&gt; response is returned along with a &lt;code&gt;"status": "complete"&lt;/code&gt; .&lt;/p&gt;

&lt;h2&gt;
  
  
  Demonstration: How Does the Verify API Use Pydantic?
&lt;/h2&gt;

&lt;p&gt;The Vonage Python SDK facilitates implementing the Vonage APIs within a Python application. Introducing Pydantic to the SDK makes it even easier to use the APIs. With Pydantic, rather than discovering an error &lt;em&gt;after&lt;/em&gt; making an API call and incurring any charges, request models are validated &lt;em&gt;before&lt;/em&gt; execution without the need for additional code to do so.&lt;/p&gt;

&lt;p&gt;The demonstration for this blog post is a minimal 2FA application using the FastAPI framework. The end-user navigates to a web page where they enter their email address. Using the Verify API, the application then generates an OTP code which is sent to the end-user provided email address. The end-user provides that code to the application and if the Verify check is successful, the end-user is rewarded with an animated gif. &lt;/p&gt;

&lt;p&gt;If you want, you can go straight to &lt;a href="http://github.com/Vonage-Community/demo-verify-python-pydantic-example" rel="noopener noreferrer"&gt;the code and use the README to get it up and running&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following code is where the Verify request is created:&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;verify_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VerifyRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify_brand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;EmailChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;channel_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;code_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we step into the &lt;code&gt;VerifyRequest&lt;/code&gt; object, we find a Pydantic model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VerifyRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;SilentAuthChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;SmsChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;WhatsappChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;VoiceChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;EmailChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;channel_timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;code_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^[a-zA-Z0-9]{4,10}$&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@model_validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;after&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;check_silent_auth_first_if_present&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&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;workflow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;SilentAuthChannel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;VerifyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;If using Silent Authentication, it must be the first channel in the &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; list.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema translates the API request into types using Python annotation and the &lt;code&gt;Field&lt;/code&gt; function to define constraints. The &lt;code&gt;workflow&lt;/code&gt; field defines a &lt;code&gt;Union&lt;/code&gt; type of other modeled types, demonstrating how types can be nested.&lt;/p&gt;

&lt;p&gt;Lastly, the &lt;code&gt;model_validator(mode='after')&lt;/code&gt; is a Pydantic decorator that provides additional configuration for how the &lt;code&gt;VerifyRequest&lt;/code&gt; model is to be validated. In this case, the &lt;code&gt;def check_silent_auth_first_if_present&lt;/code&gt; method is executed &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;VerifyRequest&lt;/code&gt; model has been constructed to ensure that if &lt;code&gt;SilentAuthChannel&lt;/code&gt; is included in the workflow, it must be listed first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification By Email
&lt;/h3&gt;

&lt;p&gt;To complete this tutorial, you will need a &lt;a href="https://developer.vonage.com/sign-up" rel="noopener noreferrer"&gt;Vonage API account&lt;/a&gt;. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the &lt;a href="https://developer.vonage.com/en/blog/$%7BCUSTOMER_DASHBOARD_URL%7D" rel="noopener noreferrer"&gt;Vonage API Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After generating and activating a virtual environment, and installing the required dependencies per the &lt;a href="https://github.com/Vonage-Community/demo-verify-python-pydantic-example" rel="noopener noreferrer"&gt;README&lt;/a&gt;, you can run the demonstration application with the following:&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;fastapi&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will spin up a web app on &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;:&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%2Fa.storyblok.com%2Ff%2F270183%2F1544x914%2Fbeec6f2a67%2F202606_pydantic-verify_screenshot_index.png%2Fm%2Ffit-in" 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%2Fa.storyblok.com%2Ff%2F270183%2F1544x914%2Fbeec6f2a67%2F202606_pydantic-verify_screenshot_index.png%2Fm%2Ffit-in" alt="A screenshot of the index page inviting the end-user to enter their email address to try out the Vonage Verify API." width="1544" height="914"&gt;&lt;/a&gt;&lt;/p&gt;
A screenshot of the index page inviting the end-user to enter their email address to try out the Vonage Verify API.



&lt;p&gt;Clicking on &lt;strong&gt;Submit verification code&lt;/strong&gt; triggers the Verify API workflow. You should be directed to a web page where you can enter the code sent to the email address you provided:&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%2Fa.storyblok.com%2Ff%2F270183%2F1544x984%2Fa75f916c08%2F202606_pydantic-verify_screenshot_send-code.png%2Fm%2Ffit-in" 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%2Fa.storyblok.com%2Ff%2F270183%2F1544x984%2Fa75f916c08%2F202606_pydantic-verify_screenshot_send-code.png%2Fm%2Ffit-in" alt="A screenshot of the verification page inviting the end-user to enter the code they received in their email." width="1544" height="984"&gt;&lt;/a&gt;&lt;/p&gt;
A screenshot of the verification page inviting the end-user to enter the code they received in their email.



&lt;p&gt;Entering the correct code here will direct you to a success page.&lt;/p&gt;

&lt;p&gt;Under the hood, the &lt;a href="https://github.com/Vonage-Community/demo-verify-python-pydantic-example/blob/main/main.py" rel="noopener noreferrer"&gt;code makes use of the Pydantic models in the SDK&lt;/a&gt; to ensure the Verify request is correctly formed and handled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification Without Validation: Testing Without Pydantic
&lt;/h3&gt;

&lt;p&gt;Now let’s take a look at how Pydantic facilitates the Verify API.&lt;/p&gt;

&lt;p&gt;In the following code, we have two functions that perform the same task: make a request to the &lt;code&gt;/verify&lt;/code&gt; endpoint. One of the functions creates the request body manually and the other one uses Pydantic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using a manually created request body:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;without_pydantic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;jwt_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JwtClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;application_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vonage_application_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vonage_private_key_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;jwt_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_application_jwt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;payload&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;brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand&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;workflow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel&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;email&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;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel_timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel_timeout&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;code_length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code_length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.nexmo.com/v2/verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&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;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;jwt_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Using Pydantic:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;with_pydantic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Vonage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;application_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vonage_application_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vonage_private_key_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;verify_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VerifyRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;EmailChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])],&lt;/span&gt;
        &lt;span class="n"&gt;channel_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel_timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;code_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code_length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_verification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verify_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;last_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_response&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;last_response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code then uses &lt;code&gt;unittest&lt;/code&gt; to test the functions with &lt;code&gt;test_data&lt;/code&gt; that violates the API parameters:&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;test_data&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;brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;678910&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel_timeout&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;sixty&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;code_length&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;five&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the tests with the following command:&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;python&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the tests should result in two different outcomes: an error and a failure.&lt;/p&gt;

&lt;p&gt;The test for the function that uses Pydantic should return this error:&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;pydantic_core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_pydantic_core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;EmailChannel&lt;/span&gt;
&lt;span class="n"&gt;to&lt;/span&gt;
  &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;string_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;678910&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;For&lt;/span&gt; &lt;span class="n"&gt;further&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt; &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pydantic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2.13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;string_type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s important to note here is that the test &lt;em&gt;did not fail&lt;/em&gt; – instead it errored out because Pydantic caught an incorrect data type &lt;em&gt;before&lt;/em&gt; the request was made. Moreover, the error message itself provides useful information for debugging instead of a general type error message. Not only do we know that the data type provided did not meet the model requirements, we also know what the expected type is as well as the type that was passed in:&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&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;string_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;678910&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test for the function &lt;em&gt;without&lt;/em&gt; Pydantic returns a failure:&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="nb"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="n"&gt;without&lt;/span&gt; &lt;span class="n"&gt;Pydantic&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;422.&lt;/span&gt; &lt;span class="n"&gt;Expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that the request was made and, as &lt;a href="https://developer.vonage.com/en/api/verify.v2?source=verify#newRequest-responses" rel="noopener noreferrer"&gt;the API is defined&lt;/a&gt;, returned a &lt;code&gt;422&lt;/code&gt; for invalid parameters. &lt;/p&gt;

&lt;p&gt;In this limited test scenario, one bad API call is negligible – on a broader scale, this can be an expensive mistake. Using Pydantic to validate a request model before making the API call helps minimize wasted API calls, enables more graceful error handling, and facilitates developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Summary
&lt;/h2&gt;

&lt;p&gt;Pydantic offers a powerful, performant solution for data validation in Python, helping developers move beyond the potential pitfalls of duck-typing. By defining schema, constraints, and validation rules with Pydantic models, you can ensure data integrity and catch errors before API requests are ever sent. This saves you from costly invalid API calls and makes error handling more graceful.&lt;/p&gt;

&lt;p&gt;As demonstrated with the Vonage Python SDK's integration with the Verify API, Pydantic makes it straightforward to work with complex, nested request models while keeping your code readable and maintainable. With version 4 of the SDK, API responses are deserialized into fully documented Pydantic models, giving you more consistency, better tooling support, and a cleaner developer experience overall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading and References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.vonage.com/en/blog/silent-authentication-updates-in-verify-api" rel="noopener noreferrer"&gt;Silent Authentication Updates in Verify API&lt;/a&gt;: New Silent Authentication updates in Verify API improve dev/test experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.vonage.com/en/blog/build-passwordless-video-login-with-silent-auth-and-the-video-api" rel="noopener noreferrer"&gt;Build Passwordless Video Login with Silent Auth and the Video API&lt;/a&gt;: Use Silent Auth with Vonage Video to verify users automatically and start real-time sessions faster without passwords or codes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.vonage.com/en/blog/introducing-zero-tap-verification-for-whatsapp" rel="noopener noreferrer"&gt;Introducing Zero-Tap Verification for WhatsApp&lt;/a&gt;: Announcing the availability of Zero-tap verification for WhatsApp through the Vonage Verify v2 API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have a question or want to share what you're building? We invite you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Subscribe to the &lt;a href="https://developer.vonage.com/en/newsletter" rel="noopener noreferrer"&gt;Developer Newsletter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Follow us on &lt;a href="https://vonage.dev/TwitterBlog" rel="noopener noreferrer"&gt;X (formerly Twitter)&lt;/a&gt; for updates&lt;/li&gt;
&lt;li&gt;  Watch tutorials on our &lt;a href="https://vonage.dev/YouTubeBlog" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Connect with us on the &lt;a href="https://vonage.dev/LinkedinBlog" rel="noopener noreferrer"&gt;Vonage Developer page on LinkedIn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>pydantic</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: Everyone loves Pydantic</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:03:50 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-everyone-loves-pydantic-32a7</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-everyone-loves-pydantic-32a7</guid>
      <description>&lt;p&gt;We’re back with another &lt;strong&gt;Vonage Dev Discussion&lt;/strong&gt;! &lt;/p&gt;

&lt;p&gt;If you’re a Python developer, chances are you’ve heard of &lt;strong&gt;&lt;a href="https://pydantic.dev/docs/validation/latest/get-started/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Pydantic is a data validation library for Python. Since its release in 2017, Pydantic has quickly become one of the most popular dependencies in software, reaching &lt;strong&gt;&lt;em&gt;&lt;a href="https://pydantic.dev/articles/pydantic-validation-10-billion-downloads" rel="noopener noreferrer"&gt;10 billion downloads&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; in 2026 🤯 &lt;/p&gt;

&lt;p&gt;But why is Pydantic so popular? We could name a few reasons – one of them being that it helped improve our Python SDK when we did a rewrite of it a couple of years ago.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/KuYbbHR37Dg?start=278"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;But what do you think? 🤔 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do you use Pydantic? What are some good use cases for it? How would you explain Pydantic to a beginner?&lt;/strong&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9ce8d9pirh77pcmllu4.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9ce8d9pirh77pcmllu4.jpg" alt="A stylized illustration of a robot with a megaphone and a speech bubble with the following text: Why do you use Pydantic? What are some good use cases for it? How would you explain Pydantic to a beginner?" width="800" height="800"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;(By the way, if you’re totally new to Pydantic, we've got this &lt;a href="https://vonage.dev/4fx1yPe" rel="noopener noreferrer"&gt;gentle introduction&lt;/a&gt; to help you out!) &lt;/p&gt;

&lt;p&gt;Let us know your thoughts about Pydantic in the comments below! 🐍👇 &lt;/p&gt;

</description>
      <category>python</category>
      <category>pydantic</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: How AI is transforming software development</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Thu, 21 May 2026 19:25:53 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-how-ai-is-transforming-software-development-1pof</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-how-ai-is-transforming-software-development-1pof</guid>
      <description>&lt;p&gt;Welcome back to the &lt;strong&gt;Vonage Dev Discussion&lt;/strong&gt;! Recently, members of the Vonage Developer Relations team met up in New York to create content, make plans, and hang out. We even got the chance to record at a super cool studio in the Big Apple!&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%2Fubkm67q08onq4i4foxwx.jpg" 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%2Fubkm67q08onq4i4foxwx.jpg" alt="A photo of members from the Vonage Developer Relations team sitting in a recording studio." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We took the opportunity to chat about something that’s been on everybody’s mind: artificial intelligence. We talked about how AI is impacting developer relations and tech in general.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/-d9XFWm86NM?start=880"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We have a &lt;a href="https://vonage.dev/4wI4vCN" rel="noopener noreferrer"&gt;second part coming out soon&lt;/a&gt;, but in the meantime, we want to know: &lt;strong&gt;What’s one thing that AI helps you with? Where do you still prefer to do the work yourself?&lt;/strong&gt;&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%2Fgku7svth7dz5un215gf5.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%2Fgku7svth7dz5un215gf5.png" alt="A stylized robot with the questions: What’s one thing that AI helps you with? Where do you still prefer to do the work yourself?" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’d love to hear your thoughts in the comments below 🤖👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>devrel</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: Defining what an API is</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Wed, 13 May 2026 20:49:48 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-defining-what-an-api-is-3806</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-defining-what-an-api-is-3806</guid>
      <description>&lt;p&gt;This week we kicked off the first in a series of office hours. Our office hours are a judgment-free space for developers to come and ask questions about using Vonage – and get an answer live from an actual human!&lt;/p&gt;

&lt;p&gt;Being able to ask questions without fear of judgment is so important for beginner engineers and being able to &lt;em&gt;answer&lt;/em&gt; those questions is equally important for senior engineers. Have you ever noticed how explaining something makes you &lt;em&gt;really think&lt;/em&gt; about what you actually know or don’t know? It's a really good feedback loop!&lt;/p&gt;

&lt;p&gt;So here’s a chance to test your knowledge: &lt;strong&gt;How would you explain what an API is to someone? What are some examples of APIs you use?&lt;/strong&gt;&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%2Fd0niycyz5pbvuiefxlb6.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%2Fd0niycyz5pbvuiefxlb6.png" alt="A stylized robot with the questions: How would you explain what an API is to someone? What are some examples of APIs you use?" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you’re new to APIs, here’s a walkthrough of how to use one from our office hours:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/T5gPTyXGfOg?start=701"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And don’t worry! If you run into any issues, we can help you out in our &lt;a href="https://vonage.dev/4uNHahr" rel="noopener noreferrer"&gt;next office hours&lt;/a&gt;. Check out the &lt;a href="https://vonage.dev/4uGRBmQ" rel="noopener noreferrer"&gt;office hours schedule&lt;/a&gt; and make sure to subscribe to our &lt;a href="https://www.youtube.com/@VonageDev/streams" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt; for future streams and videos ♥️🤓&lt;/p&gt;

&lt;p&gt;Let's keep the API conversation going in the comments below! 💻 👇&lt;/p&gt;

</description>
      <category>vonage</category>
      <category>api</category>
      <category>discuss</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: Making mistakes</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Fri, 24 Apr 2026 20:01:26 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-making-mistakes-32mc</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-making-mistakes-32mc</guid>
      <description>&lt;p&gt;Nobody is perfect! Even the most seasoned developers have made mistakes – it’s all just part of the learning process.&lt;/p&gt;

&lt;p&gt;One mistake that is so common in software development is accidentally pushing secrets to a public repository 🙈🙉🙊&lt;/p&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/2bMfzLovLWM"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


&lt;p&gt;Fortunately, there are lots of different guardrails you can put in place to help prevent the exposure of sensitive information like API keys, and we’ve covered a few of them in &lt;a href="https://vonage.dev/48R3sWS" rel="noopener noreferrer"&gt;these posts about environment variables&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But we’re curious: &lt;strong&gt;What are some mistakes you’ve made as a developer? What did you learn from those mistakes?&lt;/strong&gt;&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%2Fb9bjudjoqch6zqyh7vtd.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%2Fb9bjudjoqch6zqyh7vtd.png" alt="A stylized illustration of the Vonage mascot with the text: What are some mistakes you’ve made as a developer? What did you learn from those mistakes?" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Share your wisdom in the comments below! 🙏👇&lt;/p&gt;

</description>
      <category>security</category>
      <category>discuss</category>
      <category>python</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: Builder pattern</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Tue, 07 Apr 2026 16:48:06 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-builder-pattern-1f45</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-builder-pattern-1f45</guid>
      <description>&lt;p&gt;Have you ever heard of the builder pattern? 🧰🏘️&lt;/p&gt;

&lt;p&gt;In software development, the builder pattern is a &lt;a href="https://vonage.dev/4c8VdpU" rel="noopener noreferrer"&gt;creational design pattern&lt;/a&gt; that lets you construct complex objects step by step. It is especially helpful when you have complex classes of objects that can be extended in many different ways.&lt;/p&gt;

&lt;p&gt;In this video, our developer advocates Guillaume Faas and Jeremy Foster discuss the builder pattern and how it is implemented in the &lt;a href="https://github.com/vonage/vonage-dotnet-sdk" rel="noopener noreferrer"&gt;Vonage .NET SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/h31xOHKJ7D8"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
The builder pattern &lt;a href="https://dev.to/tr00d/why-i-use-and-abuse-the-builder-pattern-3lbm"&gt;sparks debate in the dev community&lt;/a&gt;. Some swear by it, others see it as over-engineering. We want to know: &lt;strong&gt;What's your take?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you use the builder pattern?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you use it in your own work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How would you explain it to someone?&lt;/strong&gt;&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%2Fu5gw4srnoxebb0ulget7.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%2Fu5gw4srnoxebb0ulget7.png" alt="A graphic of the Vonage mascot with the text: Do you use the builder pattern? How do you use it in your own work? How would you explain it to someone?" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Share your experience and knowledge of the builder pattern in the comments below! &lt;/p&gt;

&lt;p&gt;⚒️👇&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: Open source</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Wed, 25 Mar 2026 16:59:12 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-open-source-ipb</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-open-source-ipb</guid>
      <description>&lt;p&gt;At &lt;a href="https://vonage.dev/41y34Zf" rel="noopener noreferrer"&gt;Women+ in Open Source Day&lt;/a&gt;, I got to try my hand at making a contribution to &lt;a href="https://vonage.dev/4uOwPmf" rel="noopener noreferrer"&gt;Apache Spark&lt;/a&gt;. While I didn’t manage to clone and build the project within the allotted time, I did get to speak with one of the project’s key contributors, &lt;a href="https://vonage.dev/4rQF26G" rel="noopener noreferrer"&gt;Holden Karau&lt;/a&gt;, and was inspired by her encouragement for all sorts of people to get involved.&lt;/p&gt;

&lt;p&gt;Contributing to open source can be intimidating! And in the age of AI, contributors from diverse perspectives are even more important.&lt;/p&gt;

&lt;p&gt;So for our second ever Dev Discussion, we want to know: &lt;strong&gt;What is your advice for getting started contributing to open source projects?&lt;/strong&gt;&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%2F9i3f701wd102z9xapcdx.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%2F9i3f701wd102z9xapcdx.png" alt="A graphic of a robot with a speech bubble that reads, " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Share your advice in the comments below along with links to some of your favorite open source projects! 🖥️ 🎉👇&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
      <category>womenintech</category>
    </item>
    <item>
      <title>The Vonage Dev Discussion: Music</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Tue, 17 Feb 2026 20:50:55 +0000</pubDate>
      <link>https://dev.to/vonagedev/the-vonage-dev-discussion-4h52</link>
      <guid>https://dev.to/vonagedev/the-vonage-dev-discussion-4h52</guid>
      <description>&lt;p&gt;Hey developers! We want to hear from you for a series we’re calling the &lt;strong&gt;Dev Discussion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What is the Dev Discussion? We want it to be a space where we can take a break and talk about the human side of software development.&lt;/p&gt;

&lt;p&gt;Our first Dev Discussion topic is about &lt;strong&gt;music&lt;/strong&gt; 🎶&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%2Fcmfl6w1ljwabvanxnoas.jpg" 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%2Fcmfl6w1ljwabvanxnoas.jpg" alt="A stylized illustration of a robot with the text: " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Speaking of music and tech, you’ve probably heard &lt;a href="https://www.qobuz.com/ie-en/album/opus-no-1-tim-carleton-darrick-deel/vxcde5txl6b9a" rel="noopener noreferrer"&gt;this track&lt;/a&gt; while waiting on hold ☎️🎵&lt;/p&gt;

&lt;p&gt;Did you know it was composed in 1989 by Darrick Deel and Tim Carleton, a &lt;a href="https://www.thisamericanlife.org/516/stuck-in-the-middle-2014/act-one-0" rel="noopener noreferrer"&gt;self described&lt;/a&gt;, “Yanni-loving computer nerd” who was just “messing around with a drum machine and a synthesizer” in his parents' garage in California?&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://vonage.dev/46NixYz" rel="noopener noreferrer"&gt;make it your hold music too&lt;/a&gt;! &lt;/p&gt;

&lt;p&gt;Besides cool drum machines and chill synthesizers, we want to know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is currently playing in your headphones?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does what you listen to change according to what you are working on?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you ever work in complete silence?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Feel free to drop those playlists in the comments below 🎶 👇&lt;/p&gt;

</description>
      <category>music</category>
      <category>discuss</category>
      <category>api</category>
    </item>
    <item>
      <title>Don’t Make Assumptions About Assertions: Even with AI you still have to write your unit tests</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Sun, 26 Oct 2025 23:40:26 +0000</pubDate>
      <link>https://dev.to/lizzzzz/dont-make-assumptions-about-assertions-even-with-ai-you-still-have-to-write-your-unit-tests-4053</link>
      <guid>https://dev.to/lizzzzz/dont-make-assumptions-about-assertions-even-with-ai-you-still-have-to-write-your-unit-tests-4053</guid>
      <description>&lt;p&gt;This blog post talks about why making assumptions about assertions makes not one “ass,” but two – which is especially true in this new age of AI. You’re not going to like this, but guess what? You still have to write your unit tests. (Sorry!)&lt;/p&gt;

&lt;p&gt;But that’s not necessarily a bad thing! If this blog post doesn’t convince you of that, then I at least hope to invite you to reconsider your feelings about unit testing by taking a closer look at the most important component of the AAA test pattern: the assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get the most out of this blog post
&lt;/h2&gt;

&lt;p&gt;This blog post is designed to accommodate many different learning styles so you can choose your own adventure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://utm.guru/ujh5X" rel="noopener noreferrer"&gt;&lt;strong&gt;Jump straight to the code&lt;/strong&gt;&lt;/a&gt;: Use the README to get it up and running locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The key to human productivity or a “disconcerting trend”?&lt;/strong&gt;: A tale of two reports and their findings about AI, developer productivity, and code churn. (And why &lt;em&gt;human generated&lt;/em&gt; tests are important.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quick review of unit tests&lt;/strong&gt;: In case you need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing unit tests doesn’t have to be horrible&lt;/strong&gt;: How we can use the assert methods included with Python’s unittest framework to make writing tests less awful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using more precise assert methods to write better unit tests&lt;/strong&gt;: A walk-through of some code in which we’ll explore the effects of using different kinds of assert methods so you can experience that “&lt;em&gt;Aha!&lt;/em&gt;” moment first-hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources and references&lt;/strong&gt;: Links to more information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="ai-generated-code"&gt;AI generated code: The key to human productivity or a “disconcerting trend”?&lt;/h2&gt;

&lt;p&gt;In June 2022, &lt;strong&gt;GitHub Copilot&lt;/strong&gt; &lt;a href="https://utm.guru/ujh57" rel="noopener noreferrer"&gt;went GA&lt;/a&gt;. A year later, in June 2023, a GitHub blog post proclaimed that “&lt;a href="https://utm.guru/ujh56" rel="noopener noreferrer"&gt;AI developer productivity benefits could &lt;strong&gt;boost global GDP by over $1.5 trillion&lt;/strong&gt;&lt;/a&gt;,” citing a February 2023 research paper about an experiment in which a group of programmers with “&lt;a href="https://utm.guru/ujh55" rel="noopener noreferrer"&gt;access to GitHub Copilot was able to complete the task [of implementing an HTTP server in JavaScript as quickly as possible] 55.8% faster&lt;/a&gt;” than a control group without help from AI.&lt;/p&gt;

&lt;p&gt;It is worth noting that of the paper’s four authors, three of them are associated with GitHub or its parent company Microsoft.&lt;/p&gt;

&lt;p&gt;In response to this “promise to increase human productivity,” &lt;strong&gt;GitClear&lt;/strong&gt;, a software engineering intelligence platform, asked the question, “&lt;strong&gt;How does this profusion of LLM generated code affect quality and maintainability?&lt;/strong&gt;” To answer that question, GitClear collected 153 million changed lines of code authored between January 2020 and December 2023, and evaluated the data for differences in code quality. At the time, it was the largest known database of highly structured code change data used for this purpose, and included repos owned by Google, Microsoft, Meta, and enterprise C-Corps.&lt;/p&gt;

&lt;p&gt;What GitClear found was “&lt;a href="https://utm.guru/ujh54" rel="noopener noreferrer"&gt;disconcerting trends for maintainability&lt;/a&gt;.” The authors of the report drew this conclusion from two factors they observed notable changes in following the general availability of GitHub Copilot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code churn&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“Code churn” refers to the percentage of lines that are reverted or updated less than two weeks after being authored. In other words, this is code that was probably authored in one sprint and then reverted or updated in the next sprint because the changes were either incomplete or erroneous. The report noted that code churn increased around the same time as the release of GitHub Copilot and projected that it would continue to do so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Added and copy-pasted code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This refers to code that is newly authored instead of code that is “updated,” “deleted,” or “moved.” The report goes on to explain that an increase in adding new code instead of refactoring existing code “resembles an itinerant contributor, prone to violate the DRY-ness of the repos visited.” (DRY is an acronym for the “Don’t Repeat Yourself” &lt;a href="https://utm.guru/ujh51" rel="noopener noreferrer"&gt;tenet of software engineering&lt;/a&gt;.) And who might that “itinerant contributor” be? Yup – AI.&lt;/p&gt;

&lt;p&gt;(No, AI didn’t write that. I’ve always been a prolific em-dash user so AI probably stole its usage &lt;em&gt;from&lt;/em&gt; &lt;em&gt;me&lt;/em&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  So what can we, as developers, take away from this?
&lt;/h3&gt;

&lt;p&gt;While both the GitHub and the GitClear reports don’t try to hide their bias or content marketing intentions, we can still glean some useful insights from them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re probably going to encounter AI generated code – whether you’re the one adding it or you’re reading/reviewing AI generated code that someone else added.&lt;/li&gt;
&lt;li&gt;I’m sorry, but &lt;strong&gt;you still have to write your unit tests&lt;/strong&gt;. Now more so than ever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;… but that’s not a bad thing. (Stay with me here.)&lt;/p&gt;

&lt;h2 id="unit-test-review"&gt;A quick review of unit tests&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is unit testing?
&lt;/h3&gt;

&lt;p&gt;Unit testing is the process of &lt;strong&gt;evaluating and verifying&lt;/strong&gt; that the &lt;strong&gt;smallest functional units of code&lt;/strong&gt; do what they are supposed to do &lt;strong&gt;individually and independently&lt;/strong&gt;. So if we have a web application that allows a user to view collections of Pokemon according to ability, color, or type, we might have a Pokemon object with methods that perform tasks such as calling a Pokemon API endpoint, processing whatever response we get back from that endpoint, and then transforming that processed response into something we can display in a browser for the user. Our unit tests would act on each method individually and independently to verify that each method performs as expected.&lt;/p&gt;

&lt;p&gt;The idea is that if each “ingredient” of a whole application is correct, then we can assume the end result will turn out the way we want. We can assume that if we have the right kind of tomato sauce, crust, and toppings, our pizza will be edible &lt;em&gt;and&lt;/em&gt; delicious.&lt;/p&gt;

&lt;p&gt;Unit tests are just one kind of software testing. There are lots of different types of tests that try to answer different types of questions such as, “Do all the different parts of this system actually work together?” and, “What happens if I throw in this totally wild edge case – will my system survive?”&lt;/p&gt;

&lt;p&gt;(This is where I trot out my software testing alignment chart because it’s probably one of the most clever things I’ve ever created and people seem to really like it!)&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%2Ftq2v4r68ccej73s7c03u.webp" 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%2Ftq2v4r68ccej73s7c03u.webp" alt="A Dungeons and Dragons style alignment chart that lists different kinds of software testing and where they fall on the spectrum of good to evil and lawful to chaotic." width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The benefits of unit tests
&lt;/h3&gt;

&lt;p&gt;The benefits of unit tests include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preventing bugs&lt;/li&gt;
&lt;li&gt;Accelerating development&lt;/li&gt;
&lt;li&gt;Saving money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the most &lt;strong&gt;compelling&lt;/strong&gt; &lt;strong&gt;benefit&lt;/strong&gt; of unit tests is they help us become &lt;strong&gt;better engineers&lt;/strong&gt;. Unit tests force us to ask ourselves, “What actually &lt;em&gt;is&lt;/em&gt; the expected behavior of this method?” In the best case scenario, unit tests reveal code smells or redundancy or unnecessary complexity that motivate us to &lt;strong&gt;refactor&lt;/strong&gt; the code under test.&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%2Fkktkhu6u6e33pnbbngfu.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%2Fkktkhu6u6e33pnbbngfu.png" alt="A LinkedIn post that says: I do not understand the average developer's revulsion toward refactoring. Refactoring has been my favorite part of being a software engineer because You learn about software architecture though re-structuring, You learn about new libraries to solve problems you are facing, You learn best practices studying how others solved your problem, You learn how to document and make your code readable, You learn to take pride in your work. I am obsessed with refactoring because when you're refactoring, you're literally learning how to write better code." width="800" height="770"&gt;&lt;/a&gt;&lt;/p&gt;
A &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7384647063281651712" rel="noopener noreferrer"&gt;LinkedIn post&lt;/a&gt; in praise of refactoring and why.



&lt;h3&gt;
  
  
  Unit tests and AI
&lt;/h3&gt;

&lt;p&gt;“But Liz!” you say, “Writing unit tests is &lt;em&gt;so tedious and boring!&lt;/em&gt; Won’t I be more productive if I get AI to write them?”&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;After all, if these LLMs are trained on millions of lines of code and all of the internet, isn’t using AI to write unit tests kind of exactly like copying and pasting a solution from Stack Overflow? That time-honored tradition of software engineering?&lt;/p&gt;

&lt;p&gt;If someone else has already figured it out, why not reuse their solution? Is that not in alignment with the DRY principle?&lt;/p&gt;

&lt;p&gt;What could go wrong?&lt;/p&gt;

&lt;p&gt;To answer that, here’s an excerpt from the GitHub paper on AI powered productivity mentioned above:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We included a test suite in the repository, comprising twelve checks for submission correctness. If a submission passes, all twelve tests we counted are successfully completed. Participants could see the tests but were unable to alter them.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In order to ensure that both the AI enabled and control groups of programmers tasked with spinning up a server did so correctly, the &lt;strong&gt;&lt;em&gt;tests were written first&lt;/em&gt;&lt;/strong&gt;. Whether the code was human or AI generated, it was &lt;strong&gt;&lt;em&gt;verified with tests provided by the researchers&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And anyway, would you really trust an LLM trained on the tests most developers write?&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%2Flw0qrpjzmolnxuwp7auk.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%2Flw0qrpjzmolnxuwp7auk.png" alt="A LinkedIn post that says: A cruel irony of coding agents is that everyone who blew off automated testing for the past 20 years is now telling the AI to do TDD all the time. But because LLMs were trained on decades of their shitty tests, the agents are also terrible at testing." width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;
A &lt;a href="https://www.linkedin.com/posts/searls_a-cruel-irony-of-coding-agents-is-that-everyone-activity-7383097357816602624-cLY5" rel="noopener noreferrer"&gt;LinkedIn post&lt;/a&gt; describing why LLMs are bad at writing tests.



&lt;h2 id="not-horrible"&gt;Writing unit tests doesn’t have to be horrible&lt;/h2&gt;

&lt;p&gt;Personally, I would love to one day achieve the disciplined zen of &lt;strong&gt;&lt;a href="https://utm.guru/ujh5Y" rel="noopener noreferrer"&gt;test driven development&lt;/a&gt;&lt;/strong&gt;, but jumping right into the application code is just so much more seductive. It’s like eating dessert first, and while eating dessert first isn’t necessarily “bad” (we’re adults who can make our own decisions), it’s probably not great for us nutritionally in the long run. So how can we write unit tests in a way that is efficient and optimized? Unit tests that are &lt;strong&gt;modular&lt;/strong&gt; and &lt;strong&gt;maintainable&lt;/strong&gt; and leverage all of the tools in our toolkit?&lt;/p&gt;

&lt;h3&gt;
  
  
  The AAAs of testing
&lt;/h3&gt;

&lt;p&gt;Typically, a test follows this pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Arrange&lt;/strong&gt;: Set up the test environment. This can include fixtures, mocks, or context managers – whatever is needed to execute the code under test. When it comes to unit tests, the test environments for each test should be isolated from each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt;: Execute the code under test. If it’s an end-to-end test, this might mean kicking off a workflow that includes multiple services and dependencies. In a unit test, however, this should be a single method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert&lt;/strong&gt;: Verify the results. Compare the expected result with the test results – did the code do what you want it to do? This is the most important part of the test and in unit tests, it is (usually) best practice to have one precise assertion per test. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping this pattern in mind can help make it easier to write unit tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don’t make assumptions about assertions
&lt;/h3&gt;

&lt;p&gt;When you make assumptions about assertions, you end up with not &lt;em&gt;one&lt;/em&gt; “ass,” but &lt;em&gt;two&lt;/em&gt;. Just because you have 100% test coverage and everything is passing, it doesn’t mean your tests are actually &lt;em&gt;meaningful&lt;/em&gt; or – and here’s the “galaxy brain” revelation for you – &lt;em&gt;maintainable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In Python’s untittest specifically, assert methods come included with the &lt;code&gt;TestCase&lt;/code&gt; class. &lt;a href="https://utm.guru/ujh52" rel="noopener noreferrer"&gt;These methods check for and report failures&lt;/a&gt;. You are probably familiar with the tried and true &lt;code&gt;assertEqual&lt;/code&gt; method, in which one argument is compared with another, and if the two do not match, result in a test failure … but did you know that there are &lt;em&gt;so many more specific and precise assertions&lt;/em&gt; available to you? All out of the box?&lt;/p&gt;

&lt;p&gt;Take a look at these!&lt;/p&gt;

&lt;p&gt;Most common assert methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;Method&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;Checks that ...&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a == b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a != b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertTrue" rel="noopener noreferrer"&gt;&lt;span&gt;assertTrue(x)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;bool(x) is True&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertFalse" rel="noopener noreferrer"&gt;&lt;span&gt;assertFalse(x)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;bool(x) is False&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIs" rel="noopener noreferrer"&gt;&lt;span&gt;assertIs(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a is b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIsNot" rel="noopener noreferrer"&gt;&lt;span&gt;assertIsNot(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a is not b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIsNone" rel="noopener noreferrer"&gt;&lt;span&gt;assertIsNone(x)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;x is None&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIsNotNone" rel="noopener noreferrer"&gt;&lt;span&gt;assertIsNotNone(x)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;x is not None&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn" rel="noopener noreferrer"&gt;&lt;span&gt;assertIn(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a in b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotIn" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotIn(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a not in b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIsInstance" rel="noopener noreferrer"&gt;&lt;span&gt;assertIsInstance(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;isinstance(a, b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotIsInstance" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotIsInstance(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;not isinstance(a, b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIsSubclass" rel="noopener noreferrer"&gt;&lt;span&gt;assertIsSubclass(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;issubclass(a, b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotIsSubclass" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotIsSubclass(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;not issubclass(a, b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Assert methods that check the production of exceptions, warnings, and log messages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Checks that ...&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises" rel="noopener noreferrer"&gt;&lt;span&gt;assertRaises(exc, fun, *args, **kwds)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;fun(*args, **kwds) raises &lt;/span&gt;&lt;em&gt;&lt;span&gt;exc&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaisesRegex" rel="noopener noreferrer"&gt;&lt;span&gt;assertRaisesRegex(exc, r, fun, *args, **kwds)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;fun(*args, **kwds) raises &lt;/span&gt;&lt;em&gt;&lt;span&gt;exc&lt;/span&gt;&lt;/em&gt;&lt;span&gt; and the message matches regex &lt;/span&gt;&lt;em&gt;&lt;span&gt;r&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertWarns" rel="noopener noreferrer"&gt;&lt;span&gt;assertWarns(warn, fun, *args, **kwds)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;fun(*args, **kwds) raises &lt;/span&gt;&lt;em&gt;&lt;span&gt;warn&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertWarnsRegex" rel="noopener noreferrer"&gt;&lt;span&gt;assertWarnsRegex(warn, r, fun, *args, **kwds)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;fun(*args, **kwds) raises &lt;/span&gt;&lt;em&gt;&lt;span&gt;warn&lt;/span&gt;&lt;/em&gt;&lt;span&gt; and the message matches regex &lt;/span&gt;&lt;em&gt;&lt;span&gt;r&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs" rel="noopener noreferrer"&gt;&lt;span&gt;assertLogs(logger, level)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;The with block logs on &lt;/span&gt;&lt;em&gt;&lt;span&gt;logger&lt;/span&gt;&lt;/em&gt;&lt;span&gt; with minimum &lt;/span&gt;&lt;em&gt;&lt;span&gt;level&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNoLogs" rel="noopener noreferrer"&gt;&lt;span&gt;assertNoLogs(logger, level)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;The with block does not log on &lt;/span&gt;&lt;em&gt;&lt;span&gt;logger&lt;/span&gt;&lt;/em&gt;&lt;span&gt; with minimum &lt;/span&gt;&lt;em&gt;&lt;span&gt;level&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises" rel="noopener noreferrer"&gt;&lt;span&gt;assertRaises(exc, fun, *args, **kwds)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;fun(*args, **kwds) raises &lt;/span&gt;&lt;em&gt;&lt;span&gt;exc&lt;/span&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even more specific checks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Checks that ...&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertAlmostEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;round(a-b, 7) == 0&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotAlmostEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotAlmostEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;round(a-b, 7) != 0&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertGreater" rel="noopener noreferrer"&gt;&lt;span&gt;assertGreater(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a &amp;gt; b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertGreaterEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertGreaterEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a &amp;gt;= b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLess" rel="noopener noreferrer"&gt;&lt;span&gt;assertLess(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a &amp;lt; b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLessEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertLessEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a &amp;lt;= b&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRegex" rel="noopener noreferrer"&gt;&lt;span&gt;assertRegex(s, r)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;r.search(s)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotRegex" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotRegex(s, r)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;not r.search(s)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertCountEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertCountEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;em&gt;&lt;span&gt;a&lt;/span&gt;&lt;/em&gt;&lt;span&gt; and &lt;/span&gt;&lt;em&gt;&lt;span&gt;b&lt;/span&gt;&lt;/em&gt;&lt;span&gt; have the same elements in the same number, regardless of their order&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertStartsWith" rel="noopener noreferrer"&gt;&lt;span&gt;assertStartsWith(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a.startswith(b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotStartsWith" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotStartsWith(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;not a.startswith(b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEndsWith" rel="noopener noreferrer"&gt;&lt;span&gt;assertEndsWith(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;a.endswith(b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotEndsWith" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotEndsWith(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;not a.endswith(b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertHasAttr" rel="noopener noreferrer"&gt;&lt;span&gt;assertHasAttr(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;hastattr(a, b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotHasAttr" rel="noopener noreferrer"&gt;&lt;span&gt;assertNotHasAttr(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;not hastattr(a, b)&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Type specific assertEqual methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;strong&gt;Compares ...&lt;/strong&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertMultiLineEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertMultiLineEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;strings&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertSequenceEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertSequenceEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;sequences&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertListEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertListEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;lists&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertTupleEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertTupleEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;tuples&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertSetEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertSetEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;sets or frozensets&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertDictEqual" rel="noopener noreferrer"&gt;&lt;span&gt;assertDictEqual(a, b)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;&lt;span&gt;dicts&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Using a more precise assert method can help refine your unit tests and make the work of writing them more efficient and optimized.&lt;/p&gt;

&lt;h2 id="using-assert-methods"&gt;Getting your hands dirty: Using more precise assert methods to write better unit tests&lt;/h2&gt;

&lt;p&gt;What I appreciate most about developers as an audience is the emphasis on &lt;em&gt;showing&lt;/em&gt; rather than &lt;em&gt;telling&lt;/em&gt; because personally, I need to see something before I believe it, too. It’s even better when I get to run the code myself and arrive at that “Aha!” moment on my own. Hands-on is the best way to learn.&lt;/p&gt;

&lt;h3&gt;
  
  
  An artisanal, handcrafted, slow coded Pokemon Flask app
&lt;/h3&gt;

&lt;p&gt;You’ve heard of “no code,” right? Well, get ready for “slow code.”&lt;/p&gt;

&lt;p&gt;I wanted to see if I could use &lt;strong&gt;Cursor&lt;/strong&gt;, an AI-powered code editor, to write my unit tests, but I needed some code to test first. I decided to code – by hand – a very simple Pokedex Flask app. Sure, I could have prompted Cursor to do it for me, but that seemed to defeat the purpose of the experiment. Nor does it really simulate a real world use case since most professional developers are probably working with existing pre-AI code, and, more than that, I &lt;em&gt;wanted&lt;/em&gt; to write some Python. Isn’t that why I do this? Because it’s enjoyable?&lt;/p&gt;

&lt;p&gt;Yeah, it’s “slow code” – and it’s important. Programming is a muscle, and if you don’t exercise it regularly, it atrophies. I understand that the craft of code is often not as important as the profit it produces, but at what cost? I could have prompted an LLM to generate this blog post, but I didn’t, because I &lt;em&gt;like writing&lt;/em&gt;. Every blog post I write myself makes me a better writer; every line of code I write makes me a better programmer. It’s that hands-on learning thing.&lt;/p&gt;

&lt;p&gt;So I wrote my app by hand, using a forked minimal Flask template to avoid the boilerplate code. I ended up with a web app that uses &lt;a href="https://utm.guru/ujh7I" rel="noopener noreferrer"&gt;an API endpoint&lt;/a&gt; to view collections of Pokemon according to ability, color, or type. I muddled through the limited JavaScript the app implements and used a Python-wrapped Bootstrap library for the styling. It’s not very complicated so using Cursor to write the unit tests should be a simple task – right?&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%2Fw95gum6u685odb247p6v.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%2Fw95gum6u685odb247p6v.png" alt="A screenshot from the handcrafted Flask app showing all the pins Pokemon" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;
All the pink Pokemon all in one place.



&lt;h3&gt;
  
  
  A look at the AI generated unit tests
&lt;/h3&gt;

&lt;p&gt;My prompt was simple: &lt;code&gt;generate unit tests for pokemon.py using unittest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at what we ended up with. Feel free to pull down the code &lt;a href="https://utm.guru/ujh5X" rel="noopener noreferrer"&gt;here&lt;/a&gt; and check it out.&lt;/p&gt;

&lt;p&gt;To start things off, let’s see if the tests pass and what kind of coverage they provide.&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="nt"&gt;----------------------------------------------------------------------&lt;/span&gt;

Ran 12 tests &lt;span class="k"&gt;in &lt;/span&gt;0.008s

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Name              Stmts   Miss  Cover

&lt;span class="nt"&gt;-------------------------------------&lt;/span&gt;

pokemon.py           29      0   100%

test_pokemon.py     147      1    99%

&lt;span class="nt"&gt;-------------------------------------&lt;/span&gt;

TOTAL               176      1    99%

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

&lt;/div&gt;



&lt;p&gt;Passing tests and 100% coverage. We’re off to a promising start … or are we? Not all coverage is created equally, so it’s worth investigating the tests themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Funky fixtures&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Set up test fixtures before each test method.&lt;/span&gt;&lt;span class="sh"&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;mock_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mock&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;mock_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&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;tearDown&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Clean up after each test method.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We begin benign enough with some test fixtures in which a mock response is created for every test method in the class. Things start to get a little “smelly” when we examine the teardown method, which is just a &lt;code&gt;pass&lt;/code&gt;. In this particular case, the mock object would already be inaccessible beyond the test function it is created within, so while the teardown ensures it is truly gone, it’s a little excessive, and renders the whole fixture moot. Test fixtures can be very useful, especially when creating isolated, independent test environments, but in this scenario, it doesn’t seem to be adding to the meaningfulness of the tests.&lt;/p&gt;

&lt;p&gt;Furthermore, mock responses are created in each of the test functions, making the fixture even more redundant. (Read more about &lt;a href="https://utm.guru/ujh50" rel="noopener noreferrer"&gt;test fixtures&lt;/a&gt; and &lt;a href="https://utm.guru/ujh5Z" rel="noopener noreferrer"&gt;mocks&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;So already we find ourselves having to refactor AI generated code.&lt;/p&gt;

&lt;p&gt;Now let’s take a look at the first test and its assertions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More maintainable, human friendly tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Like their source code, tests will need to be updated as a system evolves. The more “finding and replacing” you need to conduct, the more brittle and unreliable your tests are. Using variables instead of “magic values” can reduce the number of instances that require updating. In this example, we’ve replaced the magic test values and expected values with a variable. Our test is now more modular and easier to maintain.&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;# Act: Execute the code under test
# Test the function
&lt;/span&gt;
&lt;span class="n"&gt;test_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;test_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Assert: Check the results
&lt;/span&gt;
&lt;span class="c1"&gt;# Add a message for the assert methods
&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;For test values: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the function produced: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;TestCase&lt;/code&gt; assert methods also take a message argument: &lt;code&gt;assertEqual(arg1, arg2, msg=None)&lt;/code&gt; The value provided for &lt;code&gt;msg&lt;/code&gt; outputs when a test fails. This can give us more information about a test failure, which makes it easier to fix or debug.&lt;/p&gt;

&lt;p&gt;Let’s add a test that will fail:&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;# Add a test failure to demonstrate ouput
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;something&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;Without a message, this is what our test failure looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;AssertionError: &lt;span class="s1"&gt;'something'&lt;/span&gt; is not None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a message, and utilizing the variables we created, even our failures become helpful:&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;# Add a test failure to demonstrate ouput
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;something&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;AssertionError: &lt;span class="s1"&gt;'something'&lt;/span&gt; is not None : For &lt;span class="nb"&gt;test &lt;/span&gt;values: &lt;span class="nb"&gt;type &lt;/span&gt;the &lt;span class="k"&gt;function &lt;/span&gt;produced: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'fire'&lt;/span&gt;, &lt;span class="s1"&gt;'water'&lt;/span&gt;, &lt;span class="s1"&gt;'grass'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Too many assertions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are competing philosophies on the number of assertions that a test should contain. Some people will tell you that a unit test should have only one assertion and others might tell you that more than one is okay. When writing tests, it’s important to remember that the assert methods provided by &lt;code&gt;TestCase&lt;/code&gt; “check for and report failures.” Imagine if all of these assertions result in failures you have to then fix or debug. Do these failures actually tell us anything about the code under test?&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;# Assertions
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsInstance&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;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grass&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&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;water&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;grass&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;If we look at the code under test, a list of strings is returned. That’s it, that’s all that happens. While this code is not code you’d want to push to production, it is the code we are testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&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="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;attributes_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;attributes_list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do we really need to assert on the specific contents of the list? Especially if this particular function doesn’t do anything with those contents? We probably want to reduce the number of assertions in this test. We really only need to test whether or not the code produces a list.&lt;/p&gt;

&lt;p&gt;We can do that with &lt;code&gt;assertEqual(test_result, expected_result, msg=test_message&lt;/code&gt; or we can eliminate yet another assertion (the &lt;code&gt;assertIsInstance&lt;/code&gt;) with &lt;code&gt;assertListEqual&lt;/code&gt; which will not only compare the lists, but also verify the list type.&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertListEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t believe me? Let’s change &lt;code&gt;expected_result&lt;/code&gt; to a string and see what happens when we use &lt;code&gt;assertListEqual&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="c1"&gt;# Change `expected_result` to a string
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertListEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;grass&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Second&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;grass&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test fails. Now we’ve verified not only the test result itself, but the test result type as well.&lt;/p&gt;

&lt;p&gt;Can we eliminate another assertion? Let’s see!&lt;/p&gt;

&lt;p&gt;Let’s say we want to also make sure we don’t end up with an empty list even though we might not know the exact number of list elements we will end up with. This is where we can use &lt;code&gt;assertGreaterThan&lt;/code&gt; and create a variable &lt;code&gt;list_minimum = 0&lt;/code&gt; for the minimum value we can accept – which, in this case, is zero.&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertGreater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;list_minimum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No comment please&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is just a nit, but the AI generated tests included this comment:&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&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;water&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;grass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Order matters for this function
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in the code suggests that, so it’s just a random comment. In response, I added my own useless comment: &lt;code&gt;# No, it doesn't&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(I don’t cover the rest of the tests here, but if you &lt;a href="https://utm.guru/ujh5X" rel="noopener noreferrer"&gt;check out the code&lt;/a&gt;, I’ve commented on the parts of the tests that I would have to refactor if I wanted to make this code production ready.) &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before and after&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Comparing the test before and after, our new test is a lot more succinct, meaningful, and maintainable. Now, no matter how the source code evolves, we can rely on this test to tell us if we’ve introduced any breaking changes.&lt;/p&gt;

&lt;p&gt;Before:&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="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pokemon.requests.get&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;test_get_attributes_success&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;mock_get&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Test get_attributes function with successful API response.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Mock the response
&lt;/span&gt;    &lt;span class="n"&gt;mock_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;mock_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&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;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
                                                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;               
                                                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                                                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;
    &lt;span class="n"&gt;mock_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock_response&lt;/span&gt;

    &lt;span class="c1"&gt;# Test the function
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Assertions
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsInstance&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;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grass&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&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;water&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;grass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Order matters for this function
&lt;/span&gt;    &lt;span class="n"&gt;mock_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;After:&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="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pokemon.requests.get&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;test_get_attributes_success&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;mock_get&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Test get_attributes function with successful API response.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Mock the response
&lt;/span&gt;    &lt;span class="n"&gt;mock_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;mock_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&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;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
                                                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fire&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;               
                                                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                                                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;
    &lt;span class="n"&gt;mock_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock_response&lt;/span&gt;

    &lt;span class="c1"&gt;# Test the function
&lt;/span&gt;    &lt;span class="n"&gt;test_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;test_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Assert
&lt;/span&gt;    &lt;span class="n"&gt;assert_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;For test values: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the function produced: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;expected_result&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;fire&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;water&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;grass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;list_minimum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertGreater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;list_minimum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertListEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assert_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mock_get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  In conclusion: Unit tests are the human in the loop
&lt;/h2&gt;

&lt;p&gt;Whether your code is meticulously typed out character by character, copied and pasted from Stack Overflow, or generated by an LLM, unit tests are the quickest way to verify it operates as expected. Moreover, when we start with unit tests that are written with as much care and intention as the source code itself, we lay the foundation for efficiency and optimization which makes writing the next set of unit tests much less laborious and tedious. Solid unit tests are an investment in future productivity. While AI can “hallucinate,” it has no imagination or empathy, so it cannot write tests for the humans who will eventually be stuck deciphering test failures.&lt;/p&gt;

&lt;p&gt;What do you think? Do you think AI will get better at writing unit tests? Do you feel inspired to try out other assert methods in your testing?&lt;/p&gt;

&lt;h2 id="resources-references"&gt;Resources and references&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://utm.guru/ujh5W" rel="noopener noreferrer"&gt;Python Testing – Unit Tests, Pytest, and Best Practices - DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://utm.guru/ujh5V" rel="noopener noreferrer"&gt;The Arrange, Act, and Assert (AAA) Pattern in Unit Test Automation - Semaphore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://utm.guru/ujh5U" rel="noopener noreferrer"&gt;Python assertEqual(): Test If Two Values are Equal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://utm.guru/ujh5T" rel="noopener noreferrer"&gt;Stop requiring only one assertion per unit test: Multiple assertions are fine - Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://utm.guru/ujh5S" rel="noopener noreferrer"&gt;Mastering Unit Tests in Python: A Comprehensive Guide&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❤️ If you found this blog post helpful, please consider &lt;a href="https://utm.guru/uhSYX" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>A New Era of Code Quality: Beyond bugs and into legal license compliance and risk management</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Wed, 25 Jun 2025 18:16:46 +0000</pubDate>
      <link>https://dev.to/lizzzzz/a-new-era-of-code-quality-beyond-bugs-and-into-legal-license-compliance-and-risk-management-5g8n</link>
      <guid>https://dev.to/lizzzzz/a-new-era-of-code-quality-beyond-bugs-and-into-legal-license-compliance-and-risk-management-5g8n</guid>
      <description>&lt;p&gt;In the interconnected world of software, few applications are conjured into existence entirely from scratch. Developers consistently draw upon a vast ecosystem of open-source libraries, frameworks, and external components, all designed to expedite coding and reuse established solutions. It’s simply efficient: Why should anyone invent a solution when a perfectly good one already exists?&lt;/p&gt;

&lt;p&gt;However, while incredibly efficient, reliance on third-party components comes with its own baggage: Licensing. It may seem like a minor detail, but failure to understand or comply with the terms of even one license — among the dozens or hundreds typically found in a modern application — can lead to severe legal disputes, the forced open-sourcing of proprietary code, and substantial financial penalties. This makes license compliance a remarkably complex and unglamorous undertaking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 This article is designed to accommodate different learning and reading styles, so feel free to jump ahead:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;License and litigation and why it’s important to pay attention&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The intersection of dependency management, SBOMs, and software licenses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How license management contributes to code quality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More resources and references&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="80e1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  License and litigation and why it’s important to pay attention
&lt;/h2&gt;

&lt;p&gt;Modern software projects often have a deep “dependency tree,” where a direct dependency itself relies on other libraries (transitive dependencies). It’s easy to overlook the licenses of these indirect dependencies, leading to hidden compliance risks. Unfortunately, “I didn’t know” is not an excuse that holds up in court.&lt;/p&gt;

&lt;p&gt;Not even the biggest corporations are immune to the consequences of license mismanagement. License violations have cost companies &lt;a href="https://natlawreview.com/article/100-million-reasons-open-source-compliance" rel="noopener noreferrer"&gt;millions of dollars&lt;/a&gt;, forced code &lt;a href="https://en.wikipedia.org/wiki/Free_Software_Foundation,_Inc._v._Cisco_Systems,_Inc." rel="noopener noreferrer"&gt;disclosure&lt;/a&gt; or refactoring, injunctions, &lt;a href="https://softwarefreedom.org/news/2008/mar/17/busybox-verizon/" rel="noopener noreferrer"&gt;settlements&lt;/a&gt;, and precious time and resources that could have been spent otherwise.&lt;/p&gt;

&lt;p&gt;&lt;a id="9ede"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The intersection of dependency management, SBOMs, and software licenses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an SBOM?
&lt;/h3&gt;

&lt;p&gt;An &lt;a href="https://www.sonarsource.com/learn/software-bill-of-materials/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;SBOM&lt;/a&gt; acts as a comprehensive, machine-readable inventory of all components within a software package. Much like an ingredients list on a food product, &lt;strong&gt;a Software Bills of Materials (SBOM) details every piece of open-source and proprietary software, libraries, and modules that make up a given application, along with their versions and origins&lt;/strong&gt;. And just as a food label might list “enriched flour” and then detail its components like niacin and thiamine, an SBOM also breaks down your dependencies’ dependencies, giving you a complete picture of every ingredient. For developers, maintaining an up-to-date SBOM is crucial for visibility into their software supply chain, enabling them to track and respond to security advisories more effectively.&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%2Fcdn-images-1.medium.com%2Fmax%2F2478%2F0%2Au1V4oJ5keiAkPEfR" 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%2Fcdn-images-1.medium.com%2Fmax%2F2478%2F0%2Au1V4oJ5keiAkPEfR" alt="An example of an SBOM in JSON according to the [CycloneDX](https://cyclonedx.org/capabilities/sbom/) format." width="1239" height="1247"&gt;&lt;/a&gt;&lt;/p&gt;
An example of an SBOM in JSON according to the &lt;a href="https://cyclonedx.org/capabilities/sbom/" rel="noopener noreferrer"&gt;CycloneDX&lt;/a&gt; format.



&lt;h3&gt;
  
  
  What is dependency license management?
&lt;/h3&gt;

&lt;p&gt;As developers, we are usually pretty aware of the importance of checking our “list of ingredients” (albeit often against our will while detangling dependency conflicts) and even if we don’t always immediately address CVEs, we at least know we should probably cut a card for the task. Another aspect of dependencies that we may know less about is license management.&lt;/p&gt;

&lt;p&gt;At its core, &lt;strong&gt;a software license is a legal agreement that defines the terms under which you can use, modify, and distribute a piece of software&lt;/strong&gt;. Think of it like a contract between the creator (licensor) and the user (licensee). Most open-source software comes with specific licenses that dictate how it can be used, modified, and distributed. Failing to comply with these licenses can lead to legal complications, intellectual property disputes, and financial penalties. An SBOM can serve as a foundational tool for license compliance, providing a clear record of the licenses associated with each component. This becomes particularly important in environments where certain license types (e.g., strong copyleft licenses like GPL) may have significant implications for the broader software product.&lt;/p&gt;

&lt;p&gt;The intersection of SBOMs, &lt;a href="https://utm.guru/uiBEc" rel="noopener noreferrer"&gt;CVEs&lt;/a&gt;, and licensing forms the backbone of effective dependency management. In an era where AI hallucinations and the accidental inclusion of malicious or problematic packages are growing concerns, the practice of diligent processes for generating and analyzing SBOMs become indispensable to the production and deployment of quality code.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the different kinds of software licenses?
&lt;/h3&gt;

&lt;p&gt;These licenses are the legal frameworks that define how you can use, modify, and distribute software, whether you’re building a new application, contributing to an open-source project, or simply using a program on your computer. Understanding the distinctions between these licenses is crucial for avoiding legal pitfalls, ensuring compliance, and making informed decisions about your software projects. Licenses can be broken down into the following &lt;a href="https://docs.sonarsource.com/sonarqube-server/latest/advanced-security/managing-license-profiles-and-policies/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;broad categories&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standard permissive:&lt;/strong&gt; The most commonly used permissive licenses. They grant broad permissions to use and modify with very minimal obligations (primarily attribution) and have all the essential elements of permissive open source licenses. Examples include &lt;a href="https://tlo.mit.edu/understand-ip/exploring-mit-open-source-license-comprehensive-guide" rel="noopener noreferrer"&gt;MIT&lt;/a&gt; and &lt;a href="https://www.apache.org/licenses/LICENSE-2.0" rel="noopener noreferrer"&gt;Apache&lt;/a&gt; software licenses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Non-standard permissive:&lt;/strong&gt; Permissive licenses that lack one or more essential elements of modern permissive open source licenses, or impose complex or confusing requirements. Examples include &lt;a href="https://dev.perl.org/licenses/artistic.html" rel="noopener noreferrer"&gt;Artistic 1.0&lt;/a&gt; and the &lt;a href="https://www.wtfpl.net/" rel="noopener noreferrer"&gt;WTFPL&lt;/a&gt; software licenses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weak copyleft:&lt;/strong&gt; Weak copyleft licenses require sharing your changes and additions to the licensed software when you give copies to others. Examples include &lt;a href="https://www.gnu.org/licenses/lgpl-3.0.en.html" rel="noopener noreferrer"&gt;GNU LGPL&lt;/a&gt; and the &lt;a href="https://www.mozilla.org/en-US/MPL/" rel="noopener noreferrer"&gt;Mozilla Public License&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strong copyleft:&lt;/strong&gt; In addition to the requirements of the weak copyleft licenses, strong copyleft licenses require you to share larger programs that you build with the licensed software when you give copies to others. Examples include the GNU GPL license.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network copyleft:&lt;/strong&gt; In addition to the requirements of strong copyleft licenses, network copyleft licenses require you to share larger programs that you build with the licensed software not just when you give copies to others, but also when you run the software for others to use over the Internet or another network. Examples include the &lt;a href="https://www.gnu.org/licenses/agpl-3.0.en.html" rel="noopener noreferrer"&gt;GNU AGPL&lt;/a&gt; (Which is different from the GNU LGPL!) and the &lt;a href="https://www.mongodb.com/legal/licensing/server-side-public-license" rel="noopener noreferrer"&gt;Server-Side-Public License&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maximal copyleft:&lt;/strong&gt; Maximal copyleft licenses answer the question “When does the license require you to share?” differently than other licenses. Maximal copyleft licenses require you to share software you make with others, and to license that software alike when you do. Examples include the &lt;a href="https://paritylicense.com/" rel="noopener noreferrer"&gt;Parity&lt;/a&gt; and &lt;a href="https://opensource.org/license/RPL-1.5" rel="noopener noreferrer"&gt;Reciprocal&lt;/a&gt; software licenses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uncategorized:&lt;/strong&gt; Nonstandard licenses that do not fit into the above categories.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fcdn-images-1.medium.com%2Fmax%2F2552%2F0%2A4MxT-2eRbFruHwmD" 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%2Fcdn-images-1.medium.com%2Fmax%2F2552%2F0%2A4MxT-2eRbFruHwmD" alt="With SonarQube Software Composition Analysis, each dependency includes information for all the dependencies it relies on so you can see exactly which licenses are being introduced into your project." width="1276" height="543"&gt;&lt;/a&gt;&lt;/p&gt;
With SonarQube Software Composition Analysis, each dependency includes information for all the dependencies it relies on so you can see exactly which licenses are being introduced into your project.



&lt;p&gt;&lt;a id="29cc"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How license management contributes to code quality
&lt;/h2&gt;

&lt;p&gt;At a fundamental level, high-quality code embodies these key traits: it’s reliable, secure, and &lt;em&gt;maintainable&lt;/em&gt;. Software licensing may not be the first thing that comes to mind when considering code maintainability, but think of it this way: Much like tech debt, leaving license management for later can result in “legal debt” that almost inevitably comes to collect. Dependency trees are already thorny when it comes to keeping versions in line, just imagine having to remove dependencies due to license violations. Or worse — the possibility of being forced to recall a product (and all the effort that went into it). Decommissioned code is not maintainable code.&lt;/p&gt;

&lt;p&gt;Moreover, because you can’t oversee the licensing decisions or compliance procedures governing your dependencies’ code, you need an alternative approach to ensure &lt;em&gt;their&lt;/em&gt; potential license problems don’t jeopardize &lt;em&gt;your&lt;/em&gt; own software’s legal standing. Nowadays, a rigorous and intelligent code quality discipline absolutely must encompass legal considerations like dependency license management.&lt;/p&gt;

&lt;p&gt;SonarQube’s &lt;a href="https://www.sonarsource.com/blog/sonarqube-advanced-security-now-available/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;Software Composition Analysis&lt;/a&gt; (SCA) goes beyond just listing your project’s dependencies. It also provides a comprehensive overview of associated &lt;strong&gt;license risks&lt;/strong&gt;, complete with holistic scoring and actionable remediation steps. This allows you to prioritize and address license compliance issues according to your organization’s specific needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Want to learn more about SonarQube Software Composition Analysis? Check out a tutorial &lt;a href="https://utm.guru/uiBD3" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="d69d"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More resources and references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Read more about coding best practices &lt;a href="https://utm.guru/uiona" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read more about best practices for dependency risk management in particular &lt;a href="https://docs.sonarsource.com/sonarqube-server/latest/advanced-security/best-practices-for-managing-dependency-risks/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For a breakdown of other top security flaws and how to address them, check out &lt;a href="https://medium.com/@vilojona/top-security-flaws-hiding-in-your-code-right-now-and-how-to-fix-them-342d411f9b99" rel="noopener noreferrer"&gt;Jonathan Vila’s article&lt;/a&gt; on the topic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Want to learn more about SBOMs? &lt;a href="https://medium.com/@tahirbalarabe2/why-every-modern-software-needs-a-software-bill-of-materials-sbom-for-security-04a4fec312c8" rel="noopener noreferrer"&gt;This article&lt;/a&gt; provides a comprehensive overview.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codequality</category>
      <category>sbom</category>
      <category>sonar</category>
      <category>dependencymanagement</category>
    </item>
    <item>
      <title>A New Era of Code Quality: Beyond bugs to supply chain security and dependency health</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Wed, 25 Jun 2025 17:57:08 +0000</pubDate>
      <link>https://dev.to/lizzzzz/a-new-era-of-code-qualitybeyond-bugs-to-supply-chain-security-and-dependency-health-3p40</link>
      <guid>https://dev.to/lizzzzz/a-new-era-of-code-qualitybeyond-bugs-to-supply-chain-security-and-dependency-health-3p40</guid>
      <description>&lt;p&gt;In today’s interconnected software landscape, most applications don’t just spring up from thin air. Developers are constantly pulling in countless open-source libraries, frameworks, and other third-party components to speed things up and reuse existing solutions. And why not? If a specialized solution already exists for a particular problem, why reinvent the wheel? However, while efficient, this reliance opens up a massive new vulnerability: the software supply chain.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 This article is designed to accommodate different learning and reading styles, so feel free to jump ahead:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Weak links: Anatomy of a supply chain attack and the new threat AI poses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The intersection of dependency management, SBOMs and CVEs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A stick or a snake: A framework for evaluating risk&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How security contributes to code quality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More resources and references&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="d0f8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Weak links: Anatomy of a supply chain attack
&lt;/h2&gt;

&lt;p&gt;In the context of cybersecurity, a &lt;strong&gt;supply chain attack refers to a cyberattack that targets an organization by infiltrating less secure elements in its extended network of partners, suppliers, or components, rather than directly attacking the primary target itself&lt;/strong&gt;. Simply put, it’s like a hacker getting to you by attacking someone you do business with.&lt;/p&gt;

&lt;p&gt;The core idea is to compromise a trusted third party or a component that the target organization relies on. By doing so, the attackers can then gain unauthorized access or introduce malicious code into the target’s systems, products, or services without directly breaching their main defenses.&lt;/p&gt;

&lt;p&gt;Think of it like this: If you want to break into a heavily guarded fortress (the main target), instead of attacking the front gates, you find a weakness in the company that supplies their food or builds their walls, and use that weakness to get inside.&lt;/p&gt;

&lt;p&gt;These attacks are particularly dangerous because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;They leverage trust:&lt;/strong&gt; The malicious element comes from a source that the target organization or its customers inherently rely on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;They have a wide blast radius:&lt;/strong&gt; Compromising one component or supplier can lead to a ripple effect. That means that even if your organization wasn’t the primary target of such an attack, you can still be compromised.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;They are often difficult to detect:&lt;/strong&gt; The malicious activity might be hidden within legitimate software updates, hardware, or services, making it challenging for standard security measures to identify.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common vectors for supply chain attacks include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Software updates:&lt;/strong&gt; Injecting malware into legitimate software updates, often by compromising the software vendor’s systems or exploiting vulnerabilities in the update delivery mechanism.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open-source components:&lt;/strong&gt; Introducing malicious code into widely used open-source libraries that developers then incorporate into their applications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hardware tampering:&lt;/strong&gt; Modifying hardware components during manufacturing or shipping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compromised build tools or environments:&lt;/strong&gt; Attacking the tools or infrastructure used to build software.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Third-party service providers:&lt;/strong&gt; Gaining access through a less secure vendor that has legitimate access to the target’s systems (e.g., an IT service provider).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve seen this play out in countless high-stakes scenarios, where everyone from savvy cybercriminals to &lt;a href="https://www.upguard.com/blog/how-did-kaseya-get-hacked" rel="noopener noreferrer"&gt;highly resourced, persistent threat actors&lt;/a&gt; has &lt;a href="https://www.ivanti.com/blog/software-supply-chain-attack-risk" rel="noopener noreferrer"&gt;leveraged these dependency pathways&lt;/a&gt;. These weren’t about traditional application hacks. They were about malicious packages quietly slipped into public repositories, compromised build pipelines, or trusted updates carrying hidden payloads. And it’s not always malicious! Sometimes a supply chain vulnerability is just a &lt;a href="https://tuxcare.com/blog/navigating-the-java-supply-chain-vulnerability-the-log4j-incident/" rel="noopener noreferrer"&gt;really, really big unintentional complication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As these supply chain attacks and vulnerabilities illustrate, maintaining a clean Software Bill of Materials (SBOM) is critical to code security. An SBOM is essentially a detailed “ingredient list” for your software, identifying all its components, including open-source libraries and third-party code. This transparency helps you understand exactly what’s in your software, which is crucial because bad actors know how challenging it can be to assess, verify, and remediate these hidden supply chain vulnerabilities. In modern software, it’s a constant tightrope act of prioritization with weights that shift as you cross the wire.&lt;/p&gt;

&lt;p&gt;But don’t worry — it gets more complex!&lt;/p&gt;

&lt;h3&gt;
  
  
  First came “vibe coding” …
&lt;/h3&gt;

&lt;p&gt;“Vibe coding” is an emerging approach to software development, heavily reliant on artificial intelligence, particularly large language models (LLMs). First mentioned by AI researcher Andrej Karpathy in early 2025, “vibe coding” describes a process where developers primarily use natural language prompts — speaking or typing in plain language — to instruct AI tools to generate, refine, and debug code. The core idea allows users to focus on describing what they want the software to do (the “vibe” or intent), letting the AI handle much of the how (the actual code implementation), often without the developer needing to fully understand the generated code. This method aims to make software creation more accessible and potentially faster, especially for simpler projects or prototypes.&lt;/p&gt;

&lt;p&gt;It has been met with both a great deal of skepticism and support. Love it or hate it, &lt;a href="https://utm.guru/uiuBE" rel="noopener noreferrer"&gt;“vibe coding” is changing the way we approach the software development lifecycle&lt;/a&gt;, and with these new tools come new challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Now there is “slopsquatting”
&lt;/h3&gt;

&lt;p&gt;Coined by Python Software Foundation Developer-in-Residence Seth Larson and popularized by &lt;a href="https://mastodon.social/@andrewnez/114302875075999244" rel="noopener noreferrer"&gt;a Mastodon post&lt;/a&gt; from Ecosyste.ms creator Andrew Nesbitt, “slopsquatting” — more formally known as “package hallucination” — is a type of cybersquatting. It is the “&lt;a href="https://en.wikipedia.org/wiki/Slopsquatting" rel="noopener noreferrer"&gt;practice of registering a non-existent software package name that a LLM may hallucinate in its output&lt;/a&gt;.” At best, developers may attempt to install an AI-suggested package only to run into an annoying error; at worst, these false packages are exploited.&lt;/p&gt;

&lt;p&gt;For example, if an AI suggests using a non-existent package like &lt;code&gt;super_fast_lib&lt;/code&gt; and a malicious actor then registers that name on a public repository, a developer blindly installing it could inadvertently introduce malicious code. This is where a robust Software Bill of Materials (SBOM) becomes crucial: An SBOM would ideally reveal the presence of such an unfamiliar or newly added dependency, alerting security teams to a potential slopsquatting attempt and a vulnerability in their supply chain.&lt;/p&gt;

&lt;p&gt;Either way, slopsquatting is yet another threat to software supply chain integrity, and along with all the other risks of vibe coding, further emphasize the need for human oversight in AI-generated code.&lt;/p&gt;

&lt;p&gt;Whether it’s a package that doesn’t actually exist or a package that has been deliberately exploited, software dependencies can be a point of weakness in any system. Secure code is an important aspect of the reliability and integrity of your production systems, regardless of whether the code comes from a third-party, AI, or in-house development. Implementing best practices at every step of development helps ensure that code is not only readable and maintainable, but secure as well.&lt;/p&gt;

&lt;p&gt;&lt;a id="0f61"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The intersection of dependency management, SBOMs and CVEs
&lt;/h2&gt;

&lt;p&gt;As highlighted by some of the most notable supply chain attacks and the emerging threat of “slopsquatting,” the increasing reliance on third-party software components underscores the critical importance of robust dependency management. This discipline isn’t just about integrating external code, it’s about understanding and mitigating the inherent risks associated with it. Central to this understanding are Software Bills of Materials (SBOMs) and Common Vulnerabilities and Exposures (CVEs).&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an SBOM?
&lt;/h3&gt;

&lt;p&gt;An &lt;a href="https://www.sonarsource.com/learn/software-bill-of-materials/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;SBOM&lt;/a&gt; acts as a comprehensive, machine-readable inventory of all components within a software package. Much like an ingredients list on a food product, &lt;strong&gt;a Software Bills of Materials (SBOM) details every piece of open-source and proprietary software, libraries, and modules that make up a given application, along with their versions and origins&lt;/strong&gt;. And just as a food label might list “enriched flour” and then detail its components like niacin and thiamine, an SBOM also breaks down your dependencies’ dependencies, giving you a complete picture of every ingredient. For developers, maintaining an up-to-date SBOM is crucial for visibility into their software supply chain, enabling them to track and respond to security advisories more effectively.&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%2Fcdn-images-1.medium.com%2Fmax%2F2478%2F0%2ARy7QsiyrRTaQZWve" 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%2Fcdn-images-1.medium.com%2Fmax%2F2478%2F0%2ARy7QsiyrRTaQZWve" alt="An example of an SBOM in JSON according to the [CycloneDX](https://cyclonedx.org/capabilities/sbom/) format." width="1239" height="1247"&gt;&lt;/a&gt;&lt;/p&gt;
An example of an SBOM in JSON according to the &lt;a href="https://cyclonedx.org/capabilities/sbom/" rel="noopener noreferrer"&gt;CycloneDX&lt;/a&gt; format.



&lt;h3&gt;
  
  
  What is a CVE?
&lt;/h3&gt;

&lt;p&gt;Closely related to SBOMs are &lt;a href="https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures" rel="noopener noreferrer"&gt;Common Vulnerabilities and Exposures (CVEs)&lt;/a&gt;. &lt;strong&gt;A CVE is a publicly available identifier for a known cybersecurity vulnerability.&lt;/strong&gt; When a vulnerability is discovered in a specific software component (often a library or framework), it is assigned a CVE ID. By cross-referencing components listed in an SBOM with known CVEs, organizations can rapidly identify and prioritize patching efforts for vulnerable dependencies. This proactive approach helps to prevent exploitation by malicious actors who might otherwise leverage unpatched weaknesses. The “SunSpot” malware’s ability to operate with elevated permissions and deploy backdoors underscores the severe consequences of unaddressed vulnerabilities, whether they stem from sophisticated supply chain attacks, more common software flaws, or AI hallucinations.&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%2Fl9axa3nqvixekko61dmf.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%2Fl9axa3nqvixekko61dmf.png" alt="An example of a [CVE record](https://www.cve.org/CVERecord?id=CVE-2025-27789)." width="629" height="658"&gt;&lt;/a&gt;&lt;/p&gt;
An example of a &lt;a href="https://www.cve.org/CVERecord?id=CVE-2025-27789" rel="noopener noreferrer"&gt;CVE record&lt;/a&gt;.



&lt;p&gt;&lt;a id="1806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A stick or a snake: Not every CVE is a threat to your supply chain
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://www.cve.org/About/Metrics" rel="noopener noreferrer"&gt;CVE.org&lt;/a&gt;, 40,077 CVEs were published in 2024, which is a significant jump from 2023’s 28,961 published CVEs. Responding to each and every CVE in a supply chain is neither feasible nor practical and with FIRST (the Forum of Incident Response and Security Teams) predicting an &lt;a href="https://www.first.org/blog/20250607-Vulnerability-Forecast-for-2025" rel="noopener noreferrer"&gt;increase of CVEs in 2025&lt;/a&gt;, you need to draw on other information to determine best next steps when alerted to a CVE for one of your dependencies.&lt;/p&gt;

&lt;p&gt;Imagine you are walking down a road where snakes are known to slither and you see a stick in the distance. You might mistake it for something more dangerous than just an errant piece of wood. If you’ve had the misfortune of a snake bite, you might be biased toward assuming the stick is more than just a stick. Conversely, if you’ve never encountered a snake despite reports of their presence, you might walk too close past what you &lt;em&gt;think&lt;/em&gt; is a stick and make your last mistake ever.&lt;/p&gt;

&lt;p&gt;Not all dependency vulnerabilities and exposures pose the same risk. How a CVE may affect a system is &lt;em&gt;dependent&lt;/em&gt; (See what I did there?) on a few different factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Common Vulnerability Scoring System (CVSS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Known Exploited Vulnerabilities (KEV) catalog&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Exploit Prediction Scoring System (EPSS)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Anu6iZj2y8tf1RVfe" 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Anu6iZj2y8tf1RVfe" alt="With SonarQube Software Composition Analysis, each dependency risk is evaluated holistically. As you can see here, while the CVSS score is high, the EPSS score is closer to medium with no known exploits. This information can help you determine if this CVE is worth addressing and how." width="450" height="359"&gt;&lt;/a&gt;&lt;/p&gt;
With SonarQube Software Composition Analysis, each dependency risk is evaluated holistically. As you can see here, while the CVSS score is high, the EPSS score is closer to medium with no known exploits. This information can help you determine if this CVE is worth addressing and how.



&lt;p&gt;&lt;br&gt;&lt;br&gt;
Combining all these facets of a CVE helps provide a more holistic approach to vulnerability remediation. In order to understand how CVSS, KEV, and EPSS come together, it’s important to understand them independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the CVSS?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.first.org/cvss/v4-0/user-guide" rel="noopener noreferrer"&gt;Common Vulnerability Scoring System (CVSS)&lt;/a&gt; &lt;strong&gt;is a free and open industry standard for assessing the severity of computer system security vulnerabilities&lt;/strong&gt;. It provides a numerical score (ranging from 0.0 to 10.0) that reflects the characteristics and severity of a vulnerability. Commissioned by the National Infrastructure Advisory Council (NIAC) and now under the custodianship of the Forum of Incident Response and Security Teams (FIRST), the CVSS was introduced in 2005. Its mission is to standardize vulnerability assessments to empower organizations to evaluate and respond to risks efficiently and effectively.&lt;/p&gt;

&lt;p&gt;A CVSS score is determined by three metric groups:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Base:&lt;/strong&gt; These represent the intrinsic characteristics of a vulnerability and are constant over time and across user environments. They consider factors like attack vector, attack complexity, required privileges, user interaction, scope, and impact on confidentiality, integrity, and availability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Temporal:&lt;/strong&gt; These reflect the evolving characteristics of a vulnerability over time. They account for factors such as the maturity of the exploit code, the availability of a fix, and the level of confidence in the existence of the vulnerability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environmental:&lt;/strong&gt; These metrics allow for custom adjustments to the score based on the specific context of an organization’s environment. For example, a vulnerability might be more critical in a system handling sensitive government data than in a less critical internal tool.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To summarize, CVEs provide a unique identifier for a publicly known security flaw while the CVSS provides the numerical severity rating that helps organizations assess the risk associated with a CVE within their specific context. CVSS scores serve as a vital tool for making informed decisions about which dependencies to use, which to patch first, and how to allocate limited security resources most effectively.&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Ag3SfLeFGzVNR6Oix" 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Ag3SfLeFGzVNR6Oix" alt="An example of a [CVSS score](https://www.cve.org/CVERecord?id=CVE-2025-27789)." width="603" height="173"&gt;&lt;/a&gt;&lt;/p&gt;
An example of a &lt;a href="https://www.cve.org/CVERecord?id=CVE-2025-27789" rel="noopener noreferrer"&gt;CVSS score&lt;/a&gt;.



&lt;h3&gt;
  
  
  What is a KEV?
&lt;/h3&gt;

&lt;p&gt;While the CVSS provides a crucial framework for assessing the technical severity of a vulnerability, it doesn’t inherently tell you if that vulnerability is actively being used by attackers “in the wild.” This is where the concept of a Known Exploited Vulnerabilities (KEV) catalog becomes useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A KEV catalog is a curated list of vulnerabilities that are known to have been actively exploited by threat actors&lt;/strong&gt;. Unlike a comprehensive database of all discovered vulnerabilities (which can number in the hundreds of thousands), a KEV catalog focuses on the subset of vulnerabilities that pose an immediate and proven threat because they have already been leveraged in real-world attacks.&lt;/p&gt;

&lt;p&gt;The most prominent and widely referenced KEV catalog is maintained by the &lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog" rel="noopener noreferrer"&gt;U.S. Cybersecurity and Infrastructure Security Agency (CISA)&lt;/a&gt;. The CISA’s KEV contributes critically to cybersecurity by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prioritizing remediation:&lt;/strong&gt; If a dependency in your software has a CVE that appears on the KEV list, it signifies an urgent need for patching or mitigation, regardless of its CVSS score. (Not surprisingly KEVs often have high CVSS scores.) The fact that a vulnerability has been exploited means it’s not a theoretical risk, but a clear and present danger.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shifting from reactive to proactive defense:&lt;/strong&gt; By focusing on vulnerabilities that attackers are already exploiting, organizations can more effectively cut through the noise of countless theoretical vulnerabilities and target efforts where they will have the most impact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Providing actionable intelligence:&lt;/strong&gt; The KEV catalog is not just a list; it provides actionable intelligence to defenders. It serves as a clear signal from a trusted authority that these specific vulnerabilities are being weaponized and require immediate attention.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For federal agencies and — increasingly — for private sector organizations, vulnerabilities listed in the CISA KEV catalog are considered extremely high-priority. Without this particular KEV catalog, organizations may be forced to cross reference several different sources in order to &lt;em&gt;not waste&lt;/em&gt; precious resources chasing after every single risk in an increasingly insecure cyber landscape.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the EPSS model?
&lt;/h3&gt;

&lt;p&gt;In the ongoing battle to secure complex software supply chains, simply knowing about vulnerabilities (CVEs) and their technical severity (CVSS), or even if they’ve been exploited (KEV), isn’t always enough to make the most informed decisions. Security teams need to understand the likelihood that a newly disclosed vulnerability will be exploited in the near future. This is precisely the gap that the &lt;a href="https://www.first.org/epss/model" rel="noopener noreferrer"&gt;Exploit Prediction Scoring System (EPSS)&lt;/a&gt; model aims to fill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The EPSS is an open, data-driven, and predictive scoring system that estimates the probability of a software vulnerability being exploited in the wild within the next 30 days&lt;/strong&gt;. Developed by FIRST — the same organization behind CVSS — EPSS provides a crucial layer of intelligence that complements existing vulnerability management tools.&lt;/p&gt;

&lt;p&gt;Unlike the CVSS, which scores the severity of a vulnerability’s potential impact, EPSS focuses exclusively on the &lt;em&gt;probability&lt;/em&gt; that it will be actively exploited. A vulnerability might have a high CVSS score, but if no one is &lt;em&gt;actually&lt;/em&gt; exploiting it, its immediate risk profile might be lower than a medium-CVSS vulnerability that has a high EPSS score.&lt;/p&gt;

&lt;p&gt;The EPSS estimate is updated regularly based on the following criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The existence of publicly available exploit code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Discussions of the vulnerability on social media and dark web forums.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The age of the vulnerability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The prevalence of the affected software.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Its relationship to other exploited vulnerabilities. This dynamic nature means EPSS scores can change rapidly as new information emerges, reflecting the evolving threat landscape.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F0%2AZe7kzBfa9s8lQsf2" 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%2Fcdn-images-1.medium.com%2Fmax%2F3200%2F0%2AZe7kzBfa9s8lQsf2" alt="A diagram comparing the outcomes of [CVSS scores vs. EPSS scores](https://www.first.org/epss/model)." width="1600" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;
A diagram comparing the outcomes of &lt;a href="https://www.first.org/epss/model" rel="noopener noreferrer"&gt;CVSS scores vs. EPSS scores&lt;/a&gt;.



&lt;p&gt;&lt;br&gt;&lt;br&gt;
Combined with CVEs, a CVSS score, and data from the CISA KEV catalog, the EPSS estimate plays a vital role in an environment where the speed and sophistication of attacks — as seen in supply chain compromises — demand smarter and more efficient resource allocation to secure ever-growing and complex software dependencies.&lt;/p&gt;

&lt;p&gt;In other words, security threats contain different facets of vulnerability that together determine the risk they pose and the urgency with which they should be addressed. Once you have decided that the stick down the road is indeed a snake, your next moves are based on your proximity to the snake, whether or not the snake is poisonous, whether or not an antidote exists, and whether or not the snake is an aggressive and deadly rattle snake or a common garter snake that will mostly likely just slither away upon your approach. In moments where quick decisions can be a matter of life or death — or a matter of a quick patch or a security notice to customers — these inputs help form a framework of threat evaluation that can help streamline next steps.&lt;/p&gt;

&lt;p&gt;&lt;a id="fc14"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How security contributes to code quality
&lt;/h2&gt;

&lt;p&gt;At a fundamental level, high quality code exhibits the following traits: it is maintainable, it is reliable, and it is &lt;em&gt;secure&lt;/em&gt;. With software’s increasing power and complexity, &lt;a href="https://www.linuxfoundation.org/blog/blog/a-summary-of-census-ii-open-source-software-application-libraries-the-world-depends-on" rel="noopener noreferrer"&gt;relying on third party components is inevitable&lt;/a&gt;. And that’s not a bad thing — in fact, avoiding redundancy is a &lt;a href="https://utm.guru/uiona" rel="noopener noreferrer"&gt;good coding best practice&lt;/a&gt;. However, since you don’t get to review the pull requests that are merged into the code of your dependencies, you need some other method of ensuring that their potential lapses in code quality don’t affect the quality of &lt;em&gt;your&lt;/em&gt; code. These days, a robust and intelligent code quality discipline needs to include security considerations such as dependency vulnerability management.&lt;/p&gt;

&lt;p&gt;A tool like &lt;a href="https://www.sonarsource.com/blog/sonarqube-advanced-security-now-available/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;SonarQube Advanced Security&lt;/a&gt; and its Software Composition Analysis (SCA) enables you to not only view all the dependencies within your project, but provides you with a list of dependency risks with holistic scoring and actionable fixes so you can prioritize next steps according to the specific needs of your organization.&lt;/p&gt;

&lt;p&gt;In collaboration with &lt;a href="https://www.sonarsource.com/company/press-releases/sonar-to-acquire-tidelift/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;Tidelift and its roster of open source maintainers&lt;/a&gt;, SonarQube SCA goes beyond public databases of vulnerabilities and provides insights straight from the source. This adds a layer of specialized intelligence that can help reduce the noise often associated with dependency scanning. While the Exploit Prediction Scoring System (EPSS) offers a probability of exploitation, building on the critical “Known Exploited Vulnerabilities” (KEV) catalog, SonarQube SCA further enhances this by integrating real-world maintainer insights, providing an even more refined and actionable understanding of risk. (Because we all know how often we &lt;em&gt;actually&lt;/em&gt; look at our Dependabot alerts …)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Want to learn more about SonarQube Software Composition Analysis? Check out a tutorial &lt;a href="https://utm.guru/uiBD3" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="be6f"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More resources and references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Read more about coding best practices &lt;a href="https://utm.guru/uiona" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read more about best practices for dependency risk management in particular &lt;a href="https://docs.sonarsource.com/sonarqube-server/latest/advanced-security/best-practices-for-managing-dependency-risks/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=devto-devrel-sca" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For a breakdown of other top security flaws and how to address them, check out &lt;a href="https://medium.com/@vilojona/top-security-flaws-hiding-in-your-code-right-now-and-how-to-fix-them-342d411f9b99" rel="noopener noreferrer"&gt;Jonathan Vila’s article&lt;/a&gt; on the topic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Want to learn more about SBOMs? &lt;a href="https://medium.com/@tahirbalarabe2/why-every-modern-software-needs-a-software-bill-of-materials-sbom-for-security-04a4fec312c8" rel="noopener noreferrer"&gt;This article&lt;/a&gt; provides a comprehensive overview.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codequality</category>
      <category>sbom</category>
      <category>dependencymanagement</category>
    </item>
    <item>
      <title>Passing the Vibe Check: Navigating the changing development landscape</title>
      <dc:creator>Liz Acosta</dc:creator>
      <pubDate>Fri, 16 May 2025 16:50:04 +0000</pubDate>
      <link>https://dev.to/lizzzzz/passing-the-vibe-check-navigating-the-changing-development-landscape-939</link>
      <guid>https://dev.to/lizzzzz/passing-the-vibe-check-navigating-the-changing-development-landscape-939</guid>
      <description>&lt;p&gt;In February 2025, OpenAI researcher &lt;a href="http://archive.today/2025.03.19-071425/https://x.com/karpathy/status/1886192184808149383" rel="noopener noreferrer"&gt;Andrej Karpathy coined the term “vibe coding” on social media&lt;/a&gt;. In his post, he described his process for developing a small project — without ever writing any code of his own.&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Aa22RbcKI8lA3NcpO" 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2Aa22RbcKI8lA3NcpO" alt="The inception of the term “vibe coding.”" width="596" height="563"&gt;&lt;/a&gt;&lt;/p&gt;
The inception of the term “vibe coding.



&lt;p&gt;&lt;br&gt;&lt;br&gt;
To build his “amusing throwaway weekend project,” Karpathy didn’t even need to touch a keyboard. He used superwhisper, an AI-powered voice to text transcriptor, to dictate application requirements to Cursor Composer. Cursor Composer, the AI-powered multi-file code editor and application generator, then translated the dictated instructions into a complete web app. Karpathy adjusted the app with the “dumbest” prompts like “decrease the padding on the sidebar by half” and accepted the changes without reviewing the diffs. When Karpathy encountered an issue that Cursor couldn’t fix, the OpenAI co-founder would just “ask for random changes” until the bug went away.&lt;/p&gt;

&lt;p&gt;“It’s not really coding,” he admitted, “I just see stuff, say stuff, run stuff, and copy paste stuff, and it &lt;em&gt;mostly&lt;/em&gt; works.” (Emphasis mine.)&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AWOmR4LKCblybAutY" 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AWOmR4LKCblybAutY" alt="Look ma — no typing! A diagram of Karpathy’s vibe coding stack." width="548" height="369"&gt;&lt;/a&gt;&lt;/p&gt;
Look ma — no typing! A diagram of Karpathy’s vibe coding stack.



&lt;p&gt;&lt;br&gt;&lt;br&gt;
But the vibes were anything but good for Leonel Acevedo, CEO of EnrichLead. In March 2025, &lt;a href="http://archive.today/2025.03.17-203347/https://x.com/leojr94_/status/1900767509621674109" rel="noopener noreferrer"&gt;he posted on social media&lt;/a&gt; about the SaaS application he’d built with “zero hand written code,” concluding that everyone else could “continue to whine about it [AI] or start building.” This use of AI contrasts with Karpathy’s “throwaway” project as Acevedo was touting a production-ready SaaS application and implying a different level of robustness and real-world applicability.&lt;/p&gt;

&lt;p&gt;Just a few days later, Acevedo posted that EnrichLead was under attack with &lt;a href="http://archive.today/2025.03.17-203415/https://x.com/leojr94_/status/1901560276488511759" rel="noopener noreferrer"&gt;“random things happening”&lt;/a&gt; such as maxed-out API keys, bypassed subscriptions, and corrupted databases. Because Acevedo isn’t technical, it took him longer than he anticipated to debug the code. Eventually he discovered that his vibed code exposed important API keys, making his app vulnerable to attack.&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AQhYGAZ6-VdIaiPPU" 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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2AQhYGAZ6-VdIaiPPU" alt="The worst kind of live debugging ever." width="535" height="475"&gt;&lt;/a&gt;&lt;/p&gt;
The worst kind of live debugging ever.



&lt;p&gt;&lt;br&gt;&lt;br&gt;
Accidentally pushing a hardcoded secret can be seen as a rite of passage for developers. This mistake is so common that GitHub has automatic alerting, and remediation for unintentionally publicized keys and tokens; at Sonar, we shifted that check further left by highlighting potential vulnerabilities &lt;em&gt;&lt;a href="https://www.sonarsource.com/products/sonarlint/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=medium" rel="noopener noreferrer"&gt;right in your IDE&lt;/a&gt;&lt;/em&gt;. With the advent of “vibe coding,” incidents like the one EnrichLead experienced are likely to become more common. While it’s too soon to say if vibe coding will persist, one thing we know for sure is that AI-generated code is here and it’s here to stay.&lt;/p&gt;

&lt;p&gt;And it’s just another iteration of the continuously evolving software development lifecycle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 This article is designed to accommodate different learning and reading styles, so feel free to jump ahead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What is vibe coding?: A definition of vibe coding along with the associated tools, benefits, and disadvantages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To vibe or not to vibe?: Surviving AI as a developer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More than just vibes: How AI can help us become better developers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Try it yourself: Tools and resources for surviving vibe coding&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="1d33"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is vibe coding?
&lt;/h2&gt;

&lt;p&gt;Vibe coding — or if you prefer a less cringey name, “AI coding” — is “&lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;an AI-dependent programming technique where a person describes a problem in a few sentences as a prompt to a large language model (LLM) tuned for coding.&lt;/a&gt;” Vibe coding relies exclusively on LLMs to interpret requirements and generate entire applications accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What tools are used for vibe coding?
&lt;/h3&gt;

&lt;p&gt;The most popular vibe coding tools at the moment are &lt;strong&gt;Cursor&lt;/strong&gt; and &lt;strong&gt;Windsurf&lt;/strong&gt;. Both IDEs use Claude 3.5 Sonnet under the AI hood, but with different features and user experiences.&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%2Fyxcakohgth5e4yi3ptvi.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%2Fyxcakohgth5e4yi3ptvi.png" alt="Cursor and Windsurf use the same LLM under the hood, but each IDE has its own vibes." width="777" height="280"&gt;&lt;/a&gt;&lt;/p&gt;
Cursor and Windsurf use the same LLM under the hood, but each IDE has its own vibes.



&lt;h3&gt;
  
  
  What are the benefits of vibe coding?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lower cost of entry:&lt;/strong&gt; No coding knowledge, no problem! If you’ve got a good, well thought-out idea and can write a decent prompt, the code itself is no longer an obstacle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Higher development velocity:&lt;/strong&gt; When you can let the IDE handle all of the code, prototyping and iteration cycles become quicker and easier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;More room for complex and innovative problem solving:&lt;/strong&gt; With the burden of writing boilerplate, class setups, basic CRUD operations, and other tedious tasks lifted, developers are free to focus on more high-level logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A bridge for knowledge gaps:&lt;/strong&gt; Even the most experienced developer faces the challenges of learning new technology — AI coding can facilitate quicker onboarding by supplying the fundamentals.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fddi48o39tzchdq72ibij.gif" 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%2Fddi48o39tzchdq72ibij.gif" alt="Prompting Cursor to generate a Pokédex Flask app …" width="500" height="281"&gt;&lt;/a&gt;&lt;/p&gt;
Prompting Cursor to generate a Pokédex Flask app …



&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%2Fafy6ondgum9k8p4ns93z.gif" 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%2Fafy6ondgum9k8p4ns93z.gif" alt="… and it worked!" width="510" height="287"&gt;&lt;/a&gt;&lt;/p&gt;
… and it worked!



&lt;h3&gt;
  
  
  What are the disadvantages of vibe coding?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lack of consistency and predictability:&lt;/strong&gt; It is important to remember that the output of LLMs is not deterministic. This can lead to a codebase with varying styles, structures, and approaches, rendering the code harder to understand, maintain, and debug predictably.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An increased vector of bugs, errors, and security vulnerabilities:&lt;/strong&gt; Because AI doesn’t actually “know” anything, a prompt may generate code with issues that a more experienced developer would know to avoid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Challenges in long-term maintainability and scalability:&lt;/strong&gt; Without any context, an LLM may produce code that is inconsistent and with no consideration for future optimization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Less developer productivity:&lt;/strong&gt; AI-generated code may not be well documented, increasing developer toil while trying to refactor, debug, or extend code. Moreover, trying to refine a prompt for a more desired outcome could take more time than just writing the code manually.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fdllw451quj8ako7w9827.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%2Fdllw451quj8ako7w9827.png" alt="The problem with vibes: You probably know better than to include all your styling in your HTML template file … but AI doesn’t!" width="800" height="1423"&gt;&lt;/a&gt;&lt;/p&gt;
The problem with vibes: You probably know better than to include all your styling in your HTML template file … but AI doesn’t!



&lt;p&gt;&lt;a id="9b75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  To vibe or not to vibe? Surviving AI as a software developer
&lt;/h2&gt;

&lt;p&gt;The software development lifecycle (SDLC) refers to a structured process for designing and building software. It is a rapidly evolving landscape wherein each iteration the role of the developer changes and adapts to address the latest challenges in software engineering.&lt;/p&gt;

&lt;p&gt;In the earliest “code-and-fix” era, the developer was a lone coder, directly addressing issues as they arose with little formal process. The advent of the Waterfall model transformed the developer into a specialist within a linear process, focusing on specific stages like coding or testing after requirements were defined. Iterative and incremental models then required the developer to become more adaptable, working in smaller cycles and integrating feedback more frequently.&lt;/p&gt;

&lt;p&gt;The rise of object-oriented methodologies shifted the developer towards architect and component builder, emphasizing modularity and reusability. The Agile revolution demanded the developer become a collaborative team member, engaging in frequent communication, adapting to changing requirements, and participating in all stages of the sprint. Finally, the Lean and DevOps era positions the developer as an integrated part of the entire delivery pipeline, involved in automation, deployment, and operations, with a broader responsibility for efficiency and reliability.&lt;/p&gt;

&lt;p&gt;AI coding signals a new horizon for SDLC, and with that new day comes new opportunities for developers — as long as we’re prepared for them.&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%2Fcdn-images-1.medium.com%2Fmax%2F2600%2F0%2A5ELnc_9y96BfZgKW" 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%2Fcdn-images-1.medium.com%2Fmax%2F2600%2F0%2A5ELnc_9y96BfZgKW" alt="The only const is = “change”: The evolution of the software development lifecycle." width="1300" height="294"&gt;&lt;/a&gt;&lt;/p&gt;
The only const is = “change”: The evolution of the software development lifecycle.



&lt;h3&gt;
  
  
  The evolution of the developer
&lt;/h3&gt;

&lt;p&gt;By understanding the benefits and disadvantages of AI, you will be better equipped to continue to grow as a developer. After all — you are a developer. You’re smart and learning new technologies is what you do best.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Familiarize yourself with AI coding tools for developers.&lt;/strong&gt; Whether you call it “vibe coding” or “AI coding,” check out the different tools people are using. There’s nothing quite like hands-on experience to learn something new.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vibe, but verify.&lt;/strong&gt; Get into a good practice of double-checking the AI’s work. With AI doing most of the heavy lifting, your job is more about diligently overseeing and reviewing the code rather than manually composing it. Employing a code quality tool like &lt;a href="https://www.sonarsource.com/products/sonarqube?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=medium" rel="noopener noreferrer"&gt;SonarQube&lt;/a&gt; could have prevented EnrichLead’s secret exposure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embrace the opportunity to think more systematically.&lt;/strong&gt; With AI handling all the nitty-gritty, you can focus more on solving more complex problems with more innovative solutions. You are more free to be an architect without having to be an expert on each component.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Learn how to refine your prompts.&lt;/strong&gt; LLMs like Claude respond better to clear, concise prompts that explicitly outline what is being requested and what the expected output is. Thinking more strategically about what your requirements are, what kind of design pattern you want to follow, how you are going to test your code, and how you are going to deploy and maintain it will help inform and improve your prompting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 How could you formulate a prompt to avoid exposing secrets? What requirements or parameters would you include?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="ece1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More than just vibes: How AI can help us become better developers
&lt;/h2&gt;

&lt;p&gt;Waterfall, Agile, ad-hoc, DevOps, vibe coding — no matter what SDLC or tools we are using, as developers, we are concerned with efficiency, optimization, reliability, and maintainability. We want to make software that is useful &lt;em&gt;and&lt;/em&gt; we don’t want to get paged in the middle of the night for an incident. AI can be a valuable tool that helps remove some of the more tedious tasks associated with development and free us to think more creatively; the potential disadvantages of AI mean we have the opportunity to be more deliberate and intentional in our work.&lt;/p&gt;

&lt;p&gt;While we can’t predict exactly &lt;em&gt;how&lt;/em&gt; AI will affect the software development lifecycle, we do know that no matter what direction it takes, &lt;a href="https://www.sonarsource.com/blog/ai-code-assurance-sonar/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=medium" rel="noopener noreferrer"&gt;code quality and security&lt;/a&gt; will always be central to meaningful, robust, and reliable software.&lt;/p&gt;

&lt;p&gt;Just ask the guy at EnrichLead.&lt;/p&gt;

&lt;p&gt;&lt;a id="464b"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself: Tools and resources for surviving vibe coding
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For all you Java developers out there, Sonar developer advocate Jonathan Vila has posts on Java and code quality &lt;a href="https://medium.com/@vilojona/code-reviews-with-ai-a-developer-guide-06761b73ef54?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=medium" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interested in learning more about how Sonar can help you vibe better? Check out &lt;a href="https://www.sonarsource.com/learn/integrating-quality-gates-ci-cd-pipeline/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=medium" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Still feeling a little AI anxiety? &lt;a href="https://uxdesign.cc/cracking-the-code-of-vibe-coding-124b9288e551" rel="noopener noreferrer"&gt;This article&lt;/a&gt; by UX Collective’s Pete Sena might help alleviate that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whether we like it or not, AI is (probably) here to stay, so now’s a good time to start developing some &lt;a href="https://thenewstack.io/seven-habits-of-highly-effective-ai-coding/?s_category=Organic&amp;amp;s_source=External-Referral&amp;amp;s_origin=medium" rel="noopener noreferrer"&gt;good AI coding habits&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No matter what, &lt;a href="https://utm.guru/uiona" rel="noopener noreferrer"&gt;best practices&lt;/a&gt; will always be in style.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>bestpractices</category>
      <category>cursor</category>
    </item>
  </channel>
</rss>
