<?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</title>
    <description>The latest articles on DEV Community by Liz (@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</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>The time I screwed up a conference talk (And the talk on Pydantic I would have given)</title>
      <dc:creator>Liz</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:00:51 +0000</pubDate>
      <link>https://dev.to/lizzzzz/the-time-i-screwed-up-a-conference-talk-and-the-talk-on-pydantic-i-would-have-given-3foa</link>
      <guid>https://dev.to/lizzzzz/the-time-i-screwed-up-a-conference-talk-and-the-talk-on-pydantic-i-would-have-given-3foa</guid>
      <description>&lt;p&gt;Hi there -- I know it's been a while. A lot of things have changed and a lot of things have remained the same. I felt inspired to write today for a couple of reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I want to talk about screwing up because I screwed up yesterday and rather than let the shame beat me up, I want to transform these feelings into humility and maybe a lesson you can learn from or relate to&lt;/li&gt;
&lt;li&gt;I want to find a way to share my talk because it's a good talk and I am proud of it and you might find some of it useful&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Speaking at PyBay
&lt;/h2&gt;

&lt;p&gt;PyBay is one of my favorite conferences. I've spoken at &lt;a href="https://pybay.org/" rel="noopener noreferrer"&gt;PyBay&lt;/a&gt; three times, and each time, I've grown closer to the community. The Python community in the Bay Area is one of the most intelligent, most approachable, and most generous groups of people I've ever had the honor to know. Last year my talk was accepted as an alternate (that I ended up giving) and this year I was accepted as a primary speaker. I was thrilled. I was thrilled to give my talk and be in community. I had proposed an expanded version of a talk I'd given before at smaller meetups, so I was excited to iterate on my content and dive deeper into the subject of Pydantic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sometimes you just mess up
&lt;/h2&gt;

&lt;p&gt;I have a lot going on right now. I don't know how it happened, but in addition to professional commitments, I've found myself in leadership positions with my extracurriculars. Art building, community organizing, possibly adopting a new dog ... everything has landed within the months of October and November.&lt;/p&gt;

&lt;p&gt;But I had already given versions of the talk in June, so I wasn't making anything from scratch. I've spoken at PyBay before, so everything was familiar. I built out my slide deck, created a Jupyter notebook of code examples, and rehearsed, rehearsed, rehearsed. I curated an immaculate checklist of items to bring with me developed from years of conference experience, wrote out a detailed Trello card with addresses, directions, and attachments. I was ready to be at the venue an hour before my talk so I could settle in and catch one of the other speakers. I was the most prepared I have ever been for a talk ...&lt;/p&gt;

&lt;p&gt;... except for &lt;em&gt;one email&lt;/em&gt; that I somehow missed in all my checks and rechecks. An email reminding speakers to check in two hours prior to our scheduled talk times or else our slot would be given to an alternate. I arrived anxious but excited and proceeded to the speaker check-in table.&lt;/p&gt;

&lt;p&gt;"Oh, we gave your slot to an alternate. You were supposed to be here at 1:30. There was an email."&lt;/p&gt;

&lt;p&gt;My heart dropped. "But I ... "&lt;/p&gt;

&lt;p&gt;I scrolled through my email. And sure enough, the subject line bolded to indicate it was unread, was the message from three days prior. I read it there, at the speaker check in table, the heat of shame and embarrassment rising up in me.&lt;/p&gt;

&lt;p&gt;"I'm sorry -- we've just had issues in the past and we wanted to address that."&lt;/p&gt;

&lt;p&gt;"No, no, I get it," I managed to utter. "I understand."&lt;/p&gt;

&lt;p&gt;Fortunately, I was not alone. A friend was with me. We walked outside so I could take a breath. The conference organizer noticed me and rushed over, hugging me.&lt;/p&gt;

&lt;p&gt;"I'm so sorry," she said. "We tried to call you. We know this isn't like you. We were worried something had happened. And we needed to implement this policy and it wouldn't be fair otherwise." Her eyes were sincere.&lt;/p&gt;

&lt;p&gt;I checked my phone again and saw the missed call. My phone had never rang -- we had been crossing the Bay Bridge into San Francisco, where signal can be unreliable.&lt;/p&gt;

&lt;p&gt;I've organized events before. Executing an event like PyBay requires precise coordination, contingency plans, and difficult decisions. Last year, I was accepted as an alternate and after a speaker had to drop, I got the opportunity to give my talk.&lt;/p&gt;

&lt;p&gt;"That makes sense," I said, meaning it, trying to balance the overwhelming swell of my disappointment against the warm depth of my empathy and understanding against the hot heat of my shame and regret. "I understand."&lt;/p&gt;

&lt;p&gt;She offered some more comforting words and hugged me again, and made sure I got a conference t-shirt. There's a reason why she's such a beloved member of the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failing gracefully
&lt;/h2&gt;

&lt;p&gt;In engineering, we talk a lot about graceful failures -- how to handle stuff that goes wrong without breaking the system. I wanted to leave. I wanted to hide in the shadows of shame and regret and be alone with my rumination, pointlessly saying to myself, "If only I had just done &lt;em&gt;this&lt;/em&gt;, if only I had just done &lt;em&gt;that&lt;/em&gt;." Replay the events over and over again, the exquisite self-flagellation of trauma-induced perfectionism.&lt;/p&gt;

&lt;p&gt;But I knew that wasn't the right nor the helpful thing to do. I knew the right thing would be to show up for my community the way the community has shown up for me, and to see the conference through to the end. So I stayed. I was free to attend a talk I was interested in that originally conflicted with mine; the conference organizers had wisely accepted alternate talks. My mistake was ultimately handled gracefully. That's the sign of a well-architected system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned about myself
&lt;/h2&gt;

&lt;p&gt;Besides the obvious -- which is to not only double check but &lt;em&gt;triple check&lt;/em&gt; times -- I learned how much I love giving talks. I feel a certain comfort on stage or in front of an audience, and I find a lot of satisfaction in learning, distilling, and sharing information. I love when someone approaches me afterwards and says, "Your talk taught me something new." I didn't realize how much I had been looking forward to giving my talk until I didn't get to do it.&lt;/p&gt;

&lt;p&gt;I also learned that my investment in my community doesn't go unnoticed, and that kindness, understanding, and loyalty are a positive feedback loop. The more you give, the more you get in return. No one was mad at me, I am welcome to continue participating in these events, and I was even asked to sign a thank you card for the organizer. "We love you, Liz," he said when I asked if he was &lt;em&gt;sure&lt;/em&gt; he wanted to include my name.&lt;/p&gt;

&lt;p&gt;I am still upset about it, I am still ruminating on all the things I should have done that I didn't do. I am not &lt;em&gt;not&lt;/em&gt; beating myself up about it. The day after hasn't been easy. &lt;/p&gt;

&lt;p&gt;I &lt;em&gt;am&lt;/em&gt; trying to remember the understanding and forgiveness I was shown -- and I am trying to learn to grant myself the same kind of compassion. I hope the next time you screw up (because you &lt;em&gt;will&lt;/em&gt; screw up), you remember this post, and it's okay if you can't quite muster up the compassion and grace, but I sincerely hope you will try.&lt;/p&gt;

&lt;h2&gt;
  
  
  The talk that could have been: Even &lt;em&gt;more&lt;/em&gt; about Pydantic
&lt;/h2&gt;

&lt;p&gt;The talk extends &lt;a href="https://utm.guru/utblV" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt;, which was also &lt;a href="https://utm.guru/utblW" rel="noopener noreferrer"&gt;repurposed as a video&lt;/a&gt;. You can check out the code &lt;a href="https://vonage.dev/4osnQo6" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and the code for the extended talk &lt;a href="https://vonage.dev/4rEdwKZ" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using those resources as your foundation, here's &lt;em&gt;even more&lt;/em&gt; about Pydantic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pydantic under the hood
&lt;/h3&gt;

&lt;p&gt;At a high level, Pydantic follows the principle of "don’t just validate, parse it" –- an idea made popular by &lt;a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/" rel="noopener noreferrer"&gt;software engineer Alexis King&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The distinction is whether an input value is already the required type or whether it can be converted into that type. So instead of simply asking, "Is this input already an integer?" (&lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt;), Pydantic then asks, "Can this input be &lt;em&gt;converted&lt;/em&gt; into an integer?" If the input cannot be parsed, then Pydantic raises a &lt;code&gt;ValidationError&lt;/code&gt;. (Of course, this is in lax mode, which is Pydantic's default for type coercion.)&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%2F6opdxkms7lv7gkbcgaab.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6opdxkms7lv7gkbcgaab.png" alt="A slide illustrating how Pydantic doesn't just ask if an input already is the expected type, but whether or not it can be converted." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, all of this parsing and validation can involve substantial work when processing large or deeply nested data. Pydantic uses &lt;code&gt;pydantic-core&lt;/code&gt; –- a high-performance validation and serialization engine implemented in Rust –- to handle this.&lt;/p&gt;

&lt;p&gt;So on the top layer you've got Pydantic, which provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python-facing API&lt;/li&gt;
&lt;li&gt;Model definition&lt;/li&gt;
&lt;li&gt;A pleasant developer experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And below that, you've got &lt;code&gt;pydantic-core&lt;/code&gt;, which gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema execution engine&lt;/li&gt;
&lt;li&gt;Validation and serialization&lt;/li&gt;
&lt;li&gt;And that Rust implementation which improves performance because it runs as compiled native code, which is “closer to the machine”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, we see how the input value &lt;code&gt;"14"&lt;/code&gt; (a string) is converted to an integer according to the schema we've defined in our &lt;code&gt;Pug&lt;/code&gt; 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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&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;Field&lt;/span&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;2&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;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="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;14&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type of `name`: &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;name&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="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;type of `age`: &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;age&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="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;Creating a new Pug ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;new_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="n"&gt;name&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="n"&gt;age&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;type of `name`: &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;new_pug&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="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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;type of `age`: &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;new_pug&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="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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;Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s your new_pug &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_pug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates the following output where we can see that the input type for &lt;code&gt;age&lt;/code&gt; is a &lt;code&gt;str&lt;/code&gt;. When we check the type of the value for &lt;code&gt;age&lt;/code&gt; after creating a &lt;code&gt;Pug&lt;/code&gt;, we see that the type is &lt;code&gt;int&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;type of `name`: &amp;lt;class 'str'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;type of `age`: &amp;lt;class 'str'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Creating a new Pug ...
&lt;/span&gt;&lt;span class="gp"&gt;type of `name`: &amp;lt;class 'str'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;type of `age`: &amp;lt;class 'int'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Here's your new_pug name='Matty' age=14 color='fawn'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we create a &lt;code&gt;Pug&lt;/code&gt; class that inherits from &lt;code&gt;BaseModel&lt;/code&gt;, Pydantic generates a core schema describing the model's structure and behavior.&lt;/p&gt;

&lt;p&gt;This schema is represented as a nested dictionary and is used by &lt;code&gt;pydantic-core&lt;/code&gt; for validation and serialization. We can see that in the following example when we step into the &lt;code&gt;__pydantic_core_schema__&lt;/code&gt; attribute of &lt;code&gt;Pug&lt;/code&gt; and check its 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="nf"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__pydantic_core_schema__&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="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__pydantic_core_schema__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the output generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;{'cls': &amp;lt;class '__main__.Pug'&amp;gt;&lt;/span&gt;,
&lt;span class="go"&gt; 'config': {'title': 'Pug'},
 'custom_init': False,
&lt;/span&gt;&lt;span class="gp"&gt; 'metadata': {'pydantic_js_functions': [&amp;lt;bound method BaseModel.__get_pydantic_json_schema__ of &amp;lt;class '__main__.Pug'&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;]}&lt;/span&gt;,
&lt;span class="go"&gt; 'ref': '__main__.Pug:4336583408',
 'root_model': False,
 'schema': {'computed_fields': [],
            'fields': {'age': {'metadata': {},
                               'schema': {'type': 'int'},
                               'type': 'model-field'},
                       'color': {'metadata': {},
                                 'schema': {'default': 'fawn',
                                            'schema': {'type': 'str'},
                                            'type': 'default'},
                                 'type': 'model-field'},
                       'name': {'metadata': {'pydantic_js_updates': {'description': 'The '
                                                                                    "pug's "
                                                                                    'name'}},
                                'schema': {'min_length': 2, 'type': 'str'},
                                'type': 'model-field'}},
            'model_name': 'Pug',
            'type': 'model-fields'},
 'type': 'model'}
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;class 'dict'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we step into &lt;code&gt;__pydantic_validator__&lt;/code&gt; and &lt;code&gt;__pydantic_serializer__&lt;/code&gt;, we see that they are built on Pydantic's &lt;code&gt;pydantic-core&lt;/code&gt; machinery:&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&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;Pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__pydantic_validator__&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="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__pydantic_serializer__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the output is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;lt;class 'pydantic_core._pydantic_core.SchemaValidator'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;class 'pydantic_core._pydantic_core.SchemaSerializer'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we explicitly invoke the Pydantic validation machinery with &lt;code&gt;validate_python()&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="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="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;14&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Creating a new Pug using familiar Python object instantiation syntax ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;new_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="n"&gt;name&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="n"&gt;age&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;Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s your new_pug &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_pug&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="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;Creating a new Pug twin using the Pydantic method `validate_python` ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;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;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;name&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="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;new_pug_twin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__pydantic_validator__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate_python&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s your new_pug_twin &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_pug_twin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the output, we get the same result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Creating a new Pug using familiar Python object instantiation syntax ...
Here's your new_pug name='Matty' age=14 color='fawn'
Creating a new Pug twin using the Pydantic method `validate_python` ...
Here's your new_pug_twin name='Matty' age=14 color='fawn'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's important to note here is how we create a &lt;code&gt;Pug&lt;/code&gt; with familiar Python syntax, but by inheriting from &lt;code&gt;BaseModel&lt;/code&gt;, we are actually using Pydantic to validate our input data and create an instance of &lt;code&gt;Pug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As mentioned, Pydantic also generates structured errors as opposed to obtuse stack traces. In this example, we trigger a &lt;code&gt;ValidationError&lt;/code&gt; by providing input that cannot be parsed and does not meet the constraints we defined:&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;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;J&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fourteen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;malformed_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="n"&gt;name&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="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, we get a clear &lt;code&gt;ValidationError&lt;/code&gt; with explicit messaging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;ValidationError: 2 validation errors for Pug
name
  String should have at least 2 characters [type=string_too_short, input_value='J', input_type=str]
    For further information visit https://errors.pydantic.dev/2.13/v/string_too_short
age
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='fourteen', input_type=str]
    For further information visit https://errors.pydantic.dev/2.13/v/int_parsing
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we step into &lt;code&gt;ValidationError&lt;/code&gt; we can see the error details generated by &lt;code&gt;pydantic-core&lt;/code&gt;, rather than leaving us with an unstructured traceback:&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;try&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="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;[{'ctx': {'min_length': 2},
  'input': 'J',
  'loc': ('name',),
  'msg': 'String should have at least 2 characters',
  'type': 'string_too_short',
  'url': 'https://errors.pydantic.dev/2.13/v/string_too_short'},
 {'input': 'fourteen',
  'loc': ('age',),
  'msg': 'Input should be a valid integer, unable to parse string as an '
         'integer',
  'type': 'int_parsing',
  'url': 'https://errors.pydantic.dev/2.13/v/int_parsing'}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real world Pydantic: The Vonage Python SDK
&lt;/h3&gt;

&lt;p&gt;The following examples are from the &lt;a href="https://utm.guru/utblV" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;, so you can read more about them &lt;a href="https://utm.guru/utblV" rel="noopener noreferrer"&gt;there&lt;/a&gt;. We'll look at the significant parts.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://vonage.dev/4osnQo6" rel="noopener noreferrer"&gt;demo code&lt;/a&gt; uses Vonage Verify to create a sample email OTP authentication workflow application. You can sign up for free and try it out. (All links and steps for creating a Vonage account and application are in the &lt;a href="https://vonage.dev/4osnQo6" rel="noopener noreferrer"&gt;README&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;The Vonage Python SDk got an overhaul in 2024 and one of the big updates is its use of Pydantic for requests and responses.&lt;/p&gt;

&lt;p&gt;This is what the &lt;code&gt;VerifyRequest&lt;/code&gt; object looks like. Notice how it extends the Pydantic &lt;code&gt;BaseModel&lt;/code&gt;. The &lt;code&gt;@model_validator(mode='after')&lt;/code&gt; decorator is also provided by Pydantic for additional input validation:&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Request object for a verification request.&lt;/span&gt;&lt;span class="sh"&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;In order to see how Pydantic can serve us in the real world, let's look at this demonstration comparing the use of Pydantic to create and validate a Verify API request versus manually creating the request. It does so using &lt;code&gt;unittest&lt;/code&gt; to see what happens when we provide invalid input data:&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;TestMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_without_pydantic&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;expected_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;202&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;without_pydantic&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;test_data&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;test_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test without Pydantic failed with: &lt;/span&gt;&lt;span class="si"&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;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Expected: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_result&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="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_with_pydantic&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;expected_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;202&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;with_pydantic&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;test_data&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;test_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test with Pydantic failed with: &lt;/span&gt;&lt;span class="si"&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;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Expected: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_result&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt; that these tests don't use any mocks, so running them makes real API calls which may incur costs.&lt;/p&gt;

&lt;p&gt;Running the tests produces the following outcome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;test_with_pydantic (tests.test_main.TestMain.test_with_pydantic) ... ERROR
test_without_pydantic (tests.test_main.TestMain.test_without_pydantic) ... FAIL
&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 using Pydantic &lt;em&gt;errors out&lt;/em&gt; as opposed to &lt;em&gt;failing&lt;/em&gt;. This is because Pydantic catches the incorrect request body parameters when the &lt;code&gt;VerifyRequest&lt;/code&gt; object is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;pydantic_core._pydantic_core.ValidationError: 1 validation error for EmailChannel
to
  Input should be a valid string [type=string_type, input_value=678910, input_type=int]
    For further information visit https://errors.pydantic.dev/2.13/v/string_type
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test without Pydantic &lt;em&gt;fails&lt;/em&gt; because the result is a &lt;code&gt;422&lt;/code&gt; and not a &lt;code&gt;202&lt;/code&gt; which is the &lt;a href="https://developer.vonage.com/en/api/verify.v2?source=verify#newRequest-responses" rel="noopener noreferrer"&gt;API response for invalid parameters&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;AssertionError: 422 != 202 : Test without Pydantic failed with: 422. Expected: 202
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates that the Verify endpoint was called despite invalid parameters -- something that Pydantic caught &lt;em&gt;before&lt;/em&gt; making a call to the API. This demonstrates how Pydantic validates models at creation and can help prevent wasted API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  In summary
&lt;/h3&gt;

&lt;p&gt;In this version of the talk, we go beyond how Pydantic builds on type annotation while maintaining the flexibility of duck-typing to check the validity of any input data before executing functions on it. We take a look under the hood to see how the Rust-based &lt;code&gt;pydantic-core&lt;/code&gt; engine handles schema generation, validation, and serialization without straying from the developer-friendly experience of using a human-readable language like Python. This allows us to understand how Pydantic is ideal for implementing APIs like Vonage Verify by validating input data before any request is made. &lt;/p&gt;

&lt;h2&gt;
  
  
  Seeking validation
&lt;/h2&gt;

&lt;p&gt;I titled this talk "Seeking validation" as a pun. Writing and giving talks about technical concepts is how I process and integrate new knowledge because in order to teach, you really have to understand your subject. In other words, this process helps me &lt;em&gt;validate&lt;/em&gt; my understanding.&lt;/p&gt;

&lt;p&gt;I guess the pun is even funnier and more applicable now. I didn't validate my conference call time and, as a result, my talk didn't happen. I guess that's actually a really meta demonstration of Pydantic.&lt;/p&gt;

&lt;p&gt;When I think about it that way, maybe I didn't screw up after all.     &lt;/p&gt;

</description>
      <category>devrel</category>
      <category>techtalks</category>
      <category>python</category>
    </item>
    <item>
      <title>Seeking Validation: Data Validation in Python with Pydantic and Vonage Verify</title>
      <dc:creator>Liz</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</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" width="710" height="399"&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</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" width="710" height="399"&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</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" width="710" height="399"&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</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" width="315" height="560"&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</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" width="710" height="399"&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</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</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</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/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/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</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</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>
  </channel>
</rss>
