<?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: Alexander Mia</title>
    <description>The latest articles on DEV Community by Alexander Mia (@alexander_mia_9).</description>
    <link>https://dev.to/alexander_mia_9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2857589%2F4f9d7ba0-c529-4a47-98ad-496186839231.png</url>
      <title>DEV Community: Alexander Mia</title>
      <link>https://dev.to/alexander_mia_9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexander_mia_9"/>
    <language>en</language>
    <item>
      <title>I Hid a Web Server on My Coworker's MacBook to Make It Talk. Eight Years Later, He Still Locks His Screen.</title>
      <dc:creator>Alexander Mia</dc:creator>
      <pubDate>Thu, 14 May 2026 13:51:10 +0000</pubDate>
      <link>https://dev.to/alexander_mia_9/i-hid-a-web-server-on-my-coworkers-macbook-to-make-it-talk-eight-years-later-he-still-locks-his-h65</link>
      <guid>https://dev.to/alexander_mia_9/i-hid-a-web-server-on-my-coworkers-macbook-to-make-it-talk-eight-years-later-he-still-locks-his-h65</guid>
      <description>&lt;p&gt;Around 2018, I sat across from a coworker — let's call him D — who had a habit of stepping away for lunch with his screen wide open. I told him about it. Twice. He laughed both times and forgot the next day.&lt;/p&gt;

&lt;p&gt;So I taught him with twelve lines of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;macOS ships with a command-line utility called &lt;code&gt;say&lt;/code&gt;. You pipe text to it, and your laptop talks. Or, more accurately, your laptop talks to &lt;em&gt;everyone in the office&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The plan was simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sneak onto D's laptop while he was at lunch.&lt;/li&gt;
&lt;li&gt;Run a tiny HTTP server in the background.&lt;/li&gt;
&lt;li&gt;From my own desk, send GET requests with words I wanted his laptop to shout across the room.&lt;/li&gt;
&lt;li&gt;Wait.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The whole thing
&lt;/h2&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;subprocess&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;check_output&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match_info&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="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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="nf"&gt;check_output&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;/usr/bin/say &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&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;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_routes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;web&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="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;handle&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6063&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twelve lines. No auth. No rate limit. No input validation. (We will come back to that.)&lt;/p&gt;

&lt;p&gt;I ran it on his MacBook over lunch — &lt;code&gt;python server.py &amp;amp;&lt;/code&gt; — and walked back to my desk. Then I curled it from across the room:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;his-laptop&amp;gt;:6063/hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;His MacBook, sitting unattended on his desk, said &lt;em&gt;"hello"&lt;/em&gt; in a soft computerized voice.&lt;/p&gt;

&lt;p&gt;The whole team turned around. He was still eating his sandwich somewhere on the other side of the building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The escalation
&lt;/h2&gt;

&lt;p&gt;Over the next forty minutes I tested limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://.../I-forgot-to-lock-my-screen
curl http://.../please-lock-your-laptop
curl http://.../this-is-what-happens-when-you-do-not
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;say&lt;/code&gt; handles dashes-as-words pretty well. The laptop announced its own delinquency to the entire team. People stopped working to listen.&lt;/p&gt;

&lt;p&gt;When D came back, he stared at his laptop for ten seconds, said one word I will not repeat here, and lunged for the menu bar. The server kept running because I had backgrounded it with &lt;code&gt;&amp;amp;&lt;/code&gt;. Closing the terminal did not stop it. He had to &lt;code&gt;kill&lt;/code&gt; the PID. He learned about &lt;code&gt;lsof -i :6063&lt;/code&gt; that afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual security lesson
&lt;/h2&gt;

&lt;p&gt;The trick is funny. The underlying flaw is not.&lt;/p&gt;

&lt;p&gt;That 12-line server is what an unlocked MacBook looks like to anyone with thirty seconds of physical access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They can run arbitrary shell commands.&lt;/strong&gt; &lt;code&gt;check_output(shell=True)&lt;/code&gt; on user-supplied input is a textbook command injection. In the prank version, I controlled both sides. In a real attack, the attacker controls both sides too — because they have your laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They do not need your password.&lt;/strong&gt; The Python interpreter is already there. Homebrew is already there. &lt;code&gt;curl&lt;/code&gt; is already there. Your SSH keys are already there. Your browser's saved passwords are decryptable from a logged-in session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The server survives a screen lock.&lt;/strong&gt; Once it is running, locking your screen changes nothing. The process keeps listening. The attacker keeps having fun.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You will not notice it.&lt;/strong&gt; A Python process on port 6063 does not show up in your Dock. It does not push a notification. The only sign is the port being open — and who checks &lt;code&gt;lsof -i&lt;/code&gt; after lunch?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I exposed with &lt;code&gt;say&lt;/code&gt;, somebody else could exploit with &lt;code&gt;rm&lt;/code&gt;, with a curl to a remote webhook that exfiltrates &lt;code&gt;~/.ssh/&lt;/code&gt;, or with a &lt;code&gt;launchd&lt;/code&gt; plist that survives reboot and phones home forever. The 12 lines of Python are not the attack. &lt;strong&gt;Walking away from the keyboard is the attack.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you should actually do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set auto-lock.&lt;/strong&gt; System Settings → Lock Screen → "Require password immediately after screen saver begins." Screen saver delay: one minute, not fifteen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Hot Corners.&lt;/strong&gt; Bottom-left = lock screen. Muscle memory in a week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable FileVault.&lt;/strong&gt; If someone walks off with your laptop, they get an encrypted brick. Without it, they get your home directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Ctrl+Cmd+Q&lt;/code&gt;.&lt;/strong&gt; Built-in macOS lock shortcut. Use it every single time you stand up. Even for the coffee machine. &lt;em&gt;Especially&lt;/em&gt; for the coffee machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit listening ports.&lt;/strong&gt; Once in a while, &lt;code&gt;lsof -i -P -n | grep LISTEN&lt;/code&gt;. You will be surprised what is running.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I am writing this in 2026
&lt;/h2&gt;

&lt;p&gt;Because nothing has changed.&lt;/p&gt;

&lt;p&gt;Eight years later I still see unlocked screens at every co-working space, every conference, every startup office I walk into. The defenses are better — Touch ID, Apple Watch unlock, FileVault is on by default — but none of them help if you walk away with the session active.&lt;/p&gt;

&lt;p&gt;D locks his screen now. Every time. He has muscle memory for &lt;code&gt;Ctrl+Cmd+Q&lt;/code&gt; that he did not have before that day. We do not work together anymore, but at conferences he closes his lid before he walks to the bar. Every. Time.&lt;/p&gt;

&lt;p&gt;Sometimes the most effective security training is a laptop that announces your own negligence to a room of people you respect.&lt;/p&gt;

&lt;p&gt;Twelve lines of Python. One coworker permanently reformed. Zero incidents since.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built a Dataclass in 25 Lines of Python. Then I Found Three Bugs.</title>
      <dc:creator>Alexander Mia</dc:creator>
      <pubDate>Thu, 14 May 2026 03:59:10 +0000</pubDate>
      <link>https://dev.to/alexander_mia_9/i-built-a-dataclass-in-25-lines-of-python-then-i-found-three-bugs-kf8</link>
      <guid>https://dev.to/alexander_mia_9/i-built-a-dataclass-in-25-lines-of-python-then-i-found-three-bugs-kf8</guid>
      <description>&lt;p&gt;Python's &lt;code&gt;@dataclass&lt;/code&gt; is great, but it is a decorator. You sprinkle it on, you get &lt;code&gt;__init__&lt;/code&gt;, &lt;code&gt;__eq__&lt;/code&gt;, &lt;code&gt;__hash__&lt;/code&gt;, &lt;code&gt;__repr__&lt;/code&gt; for free. Lovely.&lt;/p&gt;

&lt;p&gt;But what if you wanted a function instead? Call it with kwargs, get back a class. No decorator, no class statement, no module-level boilerplate.&lt;/p&gt;

&lt;p&gt;Here is one in 25 lines. And here are the three bugs I found while writing this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result first
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Klass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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;b&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="c1"&gt;# fields become defaults
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&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;a&lt;/span&gt;            &lt;span class="c1"&gt;# 3
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;               &lt;span class="c1"&gt;# 1 (class-level default)
&lt;/span&gt;
&lt;span class="c1"&gt;# equality by attribute dict
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# False
&lt;/span&gt;
&lt;span class="c1"&gt;# hashable, usable as dict keys
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# False
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="c1"&gt;# True
&lt;/span&gt;
&lt;span class="c1"&gt;# strict validation
&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# NameError: Unkown argument g=3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The whole implementation
&lt;/h2&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;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__data__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;_&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DataClass&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="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt; &lt;span class="n"&gt;fields&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;class_kwargs&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;class_kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&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;NameError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unkown argument {}={}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nf"&gt;setattr&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&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;__str__&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;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;data.{}({})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&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;__class__&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="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;__repr__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__str__&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__eq__&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;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__hash__&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;return&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__data__&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;_&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire thing. No imports. No metaclass. No &lt;code&gt;__init_subclass__&lt;/code&gt; gymnastics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is happening
&lt;/h2&gt;

&lt;p&gt;Three nested layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Klass&lt;/code&gt; is a function. You call it with kwargs and it returns a class.&lt;/li&gt;
&lt;li&gt;Inside, &lt;code&gt;type("DataClass", (object,), fields)&lt;/code&gt; builds a class on the fly whose &lt;strong&gt;class-level attributes&lt;/strong&gt; are the kwargs you passed. This is the same &lt;code&gt;type()&lt;/code&gt; you use every day, except with three arguments it acts as the class constructor.&lt;/li&gt;
&lt;li&gt;Then we define an inner class &lt;code&gt;_&lt;/code&gt; that &lt;strong&gt;subclasses&lt;/strong&gt; that fresh &lt;code&gt;DataClass&lt;/code&gt;. The subclass adds &lt;code&gt;__init__&lt;/code&gt;, &lt;code&gt;__eq__&lt;/code&gt;, &lt;code&gt;__hash__&lt;/code&gt;, and a custom &lt;code&gt;__repr__&lt;/code&gt;. It returns &lt;code&gt;_&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The closure over &lt;code&gt;fields&lt;/code&gt; is doing the heavy lifting. Every method on &lt;code&gt;_&lt;/code&gt; can see the original kwargs because they are captured in the enclosing function's scope.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fields["__data__"]&lt;/code&gt; stores the original key order so &lt;code&gt;__hash__&lt;/code&gt; has a stable iteration. (This is a leftover from pre-3.7 days when dict order was not guaranteed. On modern Python you could drop it.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The trick: defaults live on the class, overrides live on the instance
&lt;/h2&gt;

&lt;p&gt;When you call &lt;code&gt;Klass(a=3)&lt;/code&gt;, &lt;code&gt;__init__&lt;/code&gt; only sets &lt;code&gt;a&lt;/code&gt; on the instance. The other field &lt;code&gt;b&lt;/code&gt; stays as a class attribute. So &lt;code&gt;Klass(a=3).b&lt;/code&gt; resolves to &lt;code&gt;2&lt;/code&gt; via normal attribute lookup, but &lt;code&gt;Klass(a=3).__dict__&lt;/code&gt; only contains &lt;code&gt;{'a': 3}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is elegant — and it is also where the bugs hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three bugs hiding in plain sight
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Bug 1: &lt;code&gt;__hash__&lt;/code&gt; ignores the instance
&lt;/h3&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;__hash__&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;return&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__data__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fields&lt;/code&gt; is the closure, not &lt;code&gt;self.__dict__&lt;/code&gt;. &lt;strong&gt;Every instance of the same class returns the same hash.&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="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python lets you have hash collisions (the hash invariant only requires that equal objects have equal hashes, not the reverse). But it means a dict full of these instances degrades to O(n) — every key collides into the same bucket. Use it for ten objects, fine. Use it for ten thousand, your dict is a linked list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 2: &lt;code&gt;__repr__&lt;/code&gt; lies
&lt;/h3&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;__str__&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;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;data.{}({})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&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;__class__&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="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints &lt;code&gt;fields&lt;/code&gt; — the closure — not the instance state. So if you do &lt;code&gt;x = Klass(a=99)&lt;/code&gt; and then &lt;code&gt;print(x)&lt;/code&gt;, you see &lt;code&gt;a: 1&lt;/code&gt;, not &lt;code&gt;a: 99&lt;/code&gt;. The repr lies about what the object actually contains.&lt;/p&gt;

&lt;p&gt;Fix: format &lt;code&gt;{**fields, **self.__dict__}&lt;/code&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 3: &lt;code&gt;__eq__&lt;/code&gt; only sees what &lt;code&gt;__init__&lt;/code&gt; set
&lt;/h3&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;__eq__&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;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Klass()&lt;/code&gt; has an empty &lt;code&gt;__dict__&lt;/code&gt; because no kwargs were passed. &lt;code&gt;Klass(a=1)&lt;/code&gt; has &lt;code&gt;{'a': 1}&lt;/code&gt;. They should be equal — both objects have effective &lt;code&gt;a == 1&lt;/code&gt; — but they compare unequal because one has the attribute in its instance dict and the other inherits it from the 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="n"&gt;Klass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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;b&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="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Klass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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;b&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="c1"&gt;# False — equal in spirit, unequal in __dict__
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix: compare resolved attribute values, e.g. &lt;code&gt;{k: getattr(self, k) for k in fields['__data__']}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is still interesting
&lt;/h2&gt;

&lt;p&gt;The bugs are real, but the pattern is genuinely useful as a teaching tool. It demonstrates four things in one tiny example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classes are first-class values.&lt;/strong&gt; A function can return a class. &lt;code&gt;type()&lt;/code&gt; is just &lt;code&gt;class&lt;/code&gt; spelled differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closures over class definitions.&lt;/strong&gt; The methods on &lt;code&gt;_&lt;/code&gt; close over &lt;code&gt;fields&lt;/code&gt; from the enclosing function — no &lt;code&gt;self.fields&lt;/code&gt; storage needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The class-vs-instance attribute split.&lt;/strong&gt; Defaults on the class, overrides on the instance — the same trick Django models and many ORMs use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why &lt;code&gt;@dataclass&lt;/code&gt; exists.&lt;/strong&gt; Writing &lt;code&gt;__eq__&lt;/code&gt;, &lt;code&gt;__hash__&lt;/code&gt;, and &lt;code&gt;__repr__&lt;/code&gt; correctly is surprisingly easy to get wrong. The standard library does it once, properly. Your 25-line version does it wrong three different ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you read the standard library's &lt;a href="https://github.com/python/cpython/blob/main/Lib/dataclasses.py" rel="noopener noreferrer"&gt;&lt;code&gt;dataclasses.py&lt;/code&gt;&lt;/a&gt;, you will see it does essentially the same thing — generate &lt;code&gt;__init__&lt;/code&gt;, &lt;code&gt;__eq__&lt;/code&gt;, &lt;code&gt;__hash__&lt;/code&gt; — but with much more care about what &lt;code&gt;__dict__&lt;/code&gt; contains, when to freeze, when to compare by tuple instead of dict, and how to handle inheritance.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for this
&lt;/h2&gt;

&lt;p&gt;Never in production. Use &lt;code&gt;@dataclass&lt;/code&gt; or &lt;a href="https://www.attrs.org/" rel="noopener noreferrer"&gt;&lt;code&gt;attrs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But as an exercise? Read it. Type it out. Find the bugs yourself. That is how you learn what &lt;code&gt;@dataclass&lt;/code&gt; is actually doing under the hood.&lt;/p&gt;

&lt;p&gt;Twenty-five lines. Three bugs. One useful lesson about Python's object model.&lt;/p&gt;

</description>
      <category>code</category>
      <category>programming</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built Rust-Style ADTs in 30 Lines of Python (Pattern Matching Works)</title>
      <dc:creator>Alexander Mia</dc:creator>
      <pubDate>Tue, 12 May 2026 12:39:56 +0000</pubDate>
      <link>https://dev.to/alexander_mia_9/i-built-rust-style-adts-in-30-lines-of-python-pattern-matching-works-41le</link>
      <guid>https://dev.to/alexander_mia_9/i-built-rust-style-adts-in-30-lines-of-python-pattern-matching-works-41le</guid>
      <description>&lt;p&gt;Sum types — also called tagged unions or algebraic data types — are the feature I miss most when I switch from Rust or Haskell back to Python. The &lt;code&gt;match&lt;/code&gt; statement landed in 3.10, but the standard library still does not give you a clean way to declare a closed set of variants where each variant carries its own fields.&lt;/p&gt;

&lt;p&gt;Here is a 30-line metaclass that fixes that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result first
&lt;/h2&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;Computation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metaclass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EnumMeta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Nothing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Case&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="nc"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&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;List&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="n"&gt;follower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Computation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Computation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;p&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;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Computation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;p&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;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Computation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nothing&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;nothing&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;Three variants. Each variant is its own type. Pattern matching destructures fields by name. No &lt;code&gt;Union&lt;/code&gt;, no &lt;code&gt;isinstance&lt;/code&gt; chains, no boilerplate constructors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole implementation
&lt;/h2&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;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Case&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;attributes&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="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnumMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&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;__new__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&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="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clsdict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;new_cls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__new__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&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="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clsdict&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;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clsdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;dc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_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="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                &lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_cls&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;dc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__match_args__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dc&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;new_cls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is happening
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Case&lt;/code&gt; is a placeholder. It records the fields a variant will carry and nothing else. &lt;code&gt;Case(target=int)&lt;/code&gt; means this variant has one field named &lt;code&gt;target&lt;/code&gt; typed as &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EnumMeta&lt;/code&gt; walks the class body when the class is constructed. For every &lt;code&gt;Case&lt;/code&gt; it finds, it does four things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Builds a dataclass&lt;/strong&gt; for that variant with &lt;code&gt;make_dataclass&lt;/code&gt;. The fields come straight from the &lt;code&gt;Case&lt;/code&gt; kwargs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inherits from the parent class.&lt;/strong&gt; &lt;code&gt;bases=(new_cls,)&lt;/code&gt; means &lt;code&gt;Computation.To&lt;/code&gt; is a subclass of &lt;code&gt;Computation&lt;/code&gt;. This is what makes &lt;code&gt;match Computation.To(...)&lt;/code&gt; work as a class pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sets &lt;code&gt;__match_args__&lt;/code&gt;.&lt;/strong&gt; This is the magic line. The &lt;code&gt;match&lt;/code&gt; statement uses &lt;code&gt;__match_args__&lt;/code&gt; to know which positional fields to destructure. Dataclasses do not get this in the right shape by default for keyword-style patterns, so we set it explicitly from the field names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replaces the &lt;code&gt;Case&lt;/code&gt; placeholder&lt;/strong&gt; on the class with the new dataclass.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After &lt;code&gt;EnumMeta&lt;/code&gt; runs, &lt;code&gt;Computation.List&lt;/code&gt; is no longer a &lt;code&gt;Case&lt;/code&gt; — it is a real dataclass type. Calling &lt;code&gt;Computation.List([1])&lt;/code&gt; constructs an instance with &lt;code&gt;targets=[1]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats the alternatives
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Enum&lt;/code&gt;&lt;/strong&gt; cannot carry per-variant fields. You would end up smuggling data through &lt;code&gt;value&lt;/code&gt; tuples and losing type information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Union[A, B, C]&lt;/code&gt; of dataclasses&lt;/strong&gt; works for pattern matching, but you have to declare each variant as a separate top-level class and then wire them into a union by hand. The variants live everywhere; the union is a comment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Libraries like &lt;code&gt;returns&lt;/code&gt; or &lt;code&gt;pyrsistent&lt;/code&gt;&lt;/strong&gt; give you sum types but pull in a dependency and an opinionated style.&lt;/p&gt;

&lt;p&gt;The metaclass approach keeps variants grouped under the parent type, so &lt;code&gt;Computation&lt;/code&gt; is a closed namespace. You read the class definition and you see every possible value the type can take. That is the property that makes ADTs useful: exhaustiveness in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This is not exhaustive checking at the type level. &lt;code&gt;mypy&lt;/code&gt; does not know &lt;code&gt;Computation&lt;/code&gt; is closed, so a missing &lt;code&gt;case&lt;/code&gt; in your &lt;code&gt;match&lt;/code&gt; will not be flagged. If you want that, add a &lt;code&gt;case _: assert_never(x)&lt;/code&gt; arm at the end.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;make_dataclass&lt;/code&gt; does not accept forward references the way a typed dataclass body does. Stick to concrete types in &lt;code&gt;Case(...)&lt;/code&gt; or pass strings and let &lt;code&gt;dataclasses&lt;/code&gt; resolve them.&lt;/p&gt;

&lt;p&gt;The variants are subclasses of the parent. That is load-bearing for &lt;code&gt;match&lt;/code&gt;, but it also means &lt;code&gt;isinstance(x, Computation)&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; for any variant, which you usually want.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for this
&lt;/h2&gt;

&lt;p&gt;When you have a small, closed set of states that each carry different data. Parser results. State machine transitions. Validation outcomes. Anywhere you would write a chain of &lt;code&gt;isinstance&lt;/code&gt; checks today.&lt;/p&gt;

&lt;p&gt;For two states or a state without data, just use a dataclass with an &lt;code&gt;Optional&lt;/code&gt;. For four or more variants with distinct payloads, the metaclass earns its keep.&lt;/p&gt;

&lt;p&gt;Thirty lines. No dependencies. Real pattern matching.&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>programming</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Meta Security Layer: What Nobody Told You About Zero-Human Companies</title>
      <dc:creator>Alexander Mia</dc:creator>
      <pubDate>Fri, 24 Apr 2026 16:27:34 +0000</pubDate>
      <link>https://dev.to/alexander_mia_9/the-meta-security-layer-what-nobody-told-you-about-zero-human-companies-3pdc</link>
      <guid>https://dev.to/alexander_mia_9/the-meta-security-layer-what-nobody-told-you-about-zero-human-companies-3pdc</guid>
      <description>&lt;p&gt;I open-sourced a little thing called &lt;a href="https://github.com/msoedov/mesa" rel="noopener noreferrer"&gt;mesa&lt;/a&gt;. It's an orchestrator, paperclip alternative — you run it on your laptop, it spawns coding agents, babysits them, wires them up to your actual work. Local dev tool. That's the whole pitch.&lt;/p&gt;

&lt;p&gt;Within a week, two strangers opened PRs to it. One of them shipped 62,000 lines of Shopify onboarding docs, finance-team agent definitions, and a marketing ops playbook, into a Go project that has nothing to do with any of that: &lt;a href="https://github.com/msoedov/mesa/pull/28" rel="noopener noreferrer"&gt;mesa#28&lt;/a&gt;. Go look. It's still open.&lt;/p&gt;

&lt;p&gt;Nobody got hacked. They were using mesa the way it's supposed to be used — running it locally, pointing agents at their own projects. The agents had push access (of course they did, that's how agents work). And somewhere between "do the work" and "commit the work," something pushed to &lt;code&gt;msoedov/mesa&lt;/code&gt; instead of to their own fork. I don't even think they noticed right away.&lt;/p&gt;

&lt;p&gt;Think about what that means for a second. A competent developer, on their own machine, using their own credentials, with their own CLAUDE.md, ended up publishing a private business playbook to a public repo they didn't own. Not because anyone attacked them. Because the stack got weird.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta security layer
&lt;/h2&gt;

&lt;p&gt;This is the part I keep coming back to. I don't think we have a name for it yet, so I'll call it the meta security layer, which I realize sounds like a conference talk, sorry.&lt;/p&gt;

&lt;p&gt;The idea is simple: we now stack models under agents under orchestrators under teams under companies. Each layer brings its own rules. And the rules do not compose.&lt;/p&gt;

&lt;p&gt;A rule at the agent level — "don't run git push," stick it in CLAUDE.md — works great for the first five minutes on one laptop. Then somebody clones the repo, their CLAUDE.md doesn't have the rule. A subagent runs without inheriting parent instructions. An MCP server returns a tool description that quietly contradicts it. A persuasive issue body asks for "just one quick PR." A template overrides the memory file. A teammate runs the same orchestrator with their own credentials attached.&lt;/p&gt;

&lt;p&gt;Instructions to a probabilistic system in markdown are not controls. They are vibes. Every time you add a layer — another agent, another teammate, another shared tool — the vibe gets a little more diluted and the blast radius gets a little bigger. Nobody did anything wrong. The thing just drifted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared data is the other one
&lt;/h2&gt;

&lt;p&gt;The other one I learned the hard way is shared data access. I used to think "my tools use my credentials" was fine. It was fine when the tool was a CLI I was typing into. It is not fine when the tool is an autonomous process reading untrusted inputs and deciding to make authenticated calls.&lt;/p&gt;

&lt;p&gt;In mesa we ended up doing two things I wish I'd done on day one instead of week three. Every agent session gets its own scoped API key — not the user's key, not a shared key, its own. And the human-facing dashboard has a completely separate auth path — a dashboard login never touches an agent context, and an agent session can't suddenly act as the operator. If one agent goes sideways, the damage is that one session. Not the org, not the other work in flight, not the human's creds.&lt;/p&gt;

&lt;p&gt;The reason this is hard to see before you get burned is that in the old world it was fine. One developer, one laptop, one key, one intention. Now "I" is a team, "my laptop" is a fleet of orchestrators, "my agent" is twelve agents in parallel, and "my intention" is whatever the agent decided about the issue it just read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The supply chain is worse than you think
&lt;/h2&gt;

&lt;p&gt;I'm not even going to do a real section on supply chain because I'd be here all day. But briefly: the classic supply chain is lockfiles, package registries, signed builds.&lt;/p&gt;

&lt;p&gt;The agent supply chain is all of that, plus the model itself, plus the runtime, plus every MCP server anyone on the team installed, plus every skill and subagent definition someone imported from GitHub, plus every repo the agent reads, plus every issue body, plus every tool description the agent sees at runtime.&lt;/p&gt;

&lt;p&gt;The agent follows instructions it encounters along the way. That's the feature. That's also the vulnerability.&lt;/p&gt;

&lt;p&gt;There is no SBOM for that yet. I don't think there can be one, not in the shape we're used to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I don't have a neat ending for this. I'm writing it because I watched two smart people accidentally leak their own work into my repo, and the uncomfortable part is that I can see how it happened and I'm not sure my own setup is much better.&lt;/p&gt;

&lt;p&gt;If you're building in this space, the thing I'd tell 2025-me is: assume your rules will be bypassed, your keys will be reused somewhere you didn't expect, and your agent will one day open a PR you didn't ask for. Not because anything broke. Because the stack got weird.&lt;/p&gt;

&lt;p&gt;Design for that day. It's closer than it looks.&lt;/p&gt;

</description>
      <category>go</category>
      <category>paperclip</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
