<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>System Design on Biraj Koirala</title><link>https://birajkoirala.com.np/tags/system-design/</link><description>Recent content in System Design on Biraj Koirala</description><generator>Source Themes academia (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>Copyright &amp;copy; {year}</copyright><lastBuildDate>Wed, 10 Sep 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://birajkoirala.com.np/tags/system-design/index.xml" rel="self" type="application/rss+xml"/><item><title>Understanding the Evolution of Web Frameworks In Python</title><link>https://birajkoirala.com.np/post/6.python-web-framework-protocol/</link><pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate><guid>https://birajkoirala.com.np/post/6.python-web-framework-protocol/</guid><description>&lt;h2>Table of Contents&lt;/h2>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#from-cgi-to-wsgi-the-synchronous-era">From CGI to WSGI: The Synchronous Era&lt;/a>&lt;/li>
&lt;li>&lt;a href="#the-rise-of-asgi-the-asynchronous-era">The Rise of ASGI: The Asynchronous Era&lt;/a>&lt;/li>
&lt;li>&lt;a href="#key-differences-wsgi-vs-asgi">Key Differences: WSGI vs ASGI&lt;/a>&lt;/li>
&lt;li>&lt;a href="#examples-in-practice">Examples in Practice&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#flask-wsgi">Flask (WSGI)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#starlette-asgi">Starlette (ASGI)&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#beyond-wsgi-and-asgi">Beyond WSGI and ASGI&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#1-the-uwsgi-protocol-not-the-same-as-wsgi">1. The uWSGI Protocol (Not the Same as WSGI!)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#2-graphql-frameworks-ariadne-strawberry-graphene">2. GraphQL Frameworks (Ariadne, Strawberry, Graphene)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#3-rpc-and-grpc">3. RPC and gRPC&lt;/a>&lt;/li>
&lt;li>&lt;a href="#4-serverless-and-function-as-a-service">4. Serverless and Function-as-a-Service&lt;/a>&lt;/li>
&lt;li>&lt;a href="#5-webassembly-wasm-and-python">5. WebAssembly (WASM) and Python&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#putting-it-together">Putting It Together&lt;/a>&lt;/li>
&lt;li>&lt;a href="#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;p>If you’ve used web frameworks like Flask or FastAPI, you may have noticed something curious: people call Flask a WSGI framework and FastAPI a ASGI framework. But what does that really mean? Why do we have these different interfaces in Python web development, and how should you choose between them?&lt;/p>
&lt;p>In this article, we’ll break down the history, architecture, and differences between WSGI and ASGI, and explain why frameworks like Flask, Django, Starlette and FastAPI exist.&lt;/p>
&lt;div class="gallery" data-gallery-id="gallery-1757987062-4">
&lt;div class="gallery-grid">
&lt;div class="gallery-item">
&lt;img src="django.png" alt="Gallery image 1" loading="lazy" onclick="openLightbox('gallery-1757987062-4', 0 , 'django.png')">
&lt;/div>
&lt;div class="gallery-item">
&lt;img src="fastapi.png" alt="Gallery image 2" loading="lazy" onclick="openLightbox('gallery-1757987062-4', 1 , 'fastapi.png')">
&lt;/div>
&lt;div class="gallery-item">
&lt;img src="flask.png" alt="Gallery image 3" loading="lazy" onclick="openLightbox('gallery-1757987062-4', 2 , 'flask.png')">
&lt;/div>
&lt;div class="gallery-item">
&lt;img src="starlette.png" alt="Gallery image 4" loading="lazy" onclick="openLightbox('gallery-1757987062-4', 3 , 'starlette.png')">
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div id="lightbox-modal-gallery-1757987062-4" class="lightbox-modal" onclick="closeLightbox('gallery-1757987062-4')">
&lt;div class="lightbox-content" onclick="event.stopPropagation()">
&lt;span class="lightbox-close" onclick="closeLightbox('gallery-1757987062-4')">&amp;times;&lt;/span>
&lt;img id="lightbox-image-gallery-1757987062-4" src="" alt="">
&lt;div class="lightbox-nav">
&lt;button class="lightbox-prev" onclick="changeImage('gallery-1757987062-4', -1)">&amp;#10094;&lt;/button>
&lt;button class="lightbox-next" onclick="changeImage('gallery-1757987062-4', 1)">&amp;#10095;&lt;/button>
&lt;/div>
&lt;div class="lightbox-counter">
&lt;span id="image-counter-gallery-1757987062-4">1 / 4&lt;/span>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;style>
.gallery {
margin: 2rem 0;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
padding: 1rem;
}
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
height: 270px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
}
.gallery-item:hover {
transform: translateY(-5px);
}
.gallery-item img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
display: block;
transition: transform 0.3s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
.gallery-item a {
display: block;
text-decoration: none;
}
&lt;/style>
&lt;style>
.lightbox-modal {
display: none;
position: fixed;
z-index: 10000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
cursor: pointer;
}
.lightbox-content {
position: relative;
margin: auto;
padding: 20px;
width: 90%;
max-width: 1200px;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
cursor: default;
}
.lightbox-close {
position: absolute;
top: 15px;
right: 35px;
color: white;
font-size: 40px;
font-weight: bold;
cursor: pointer;
z-index: 10001;
background: rgba(0, 0, 0, 0.7);
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.lightbox-close:hover {
background: rgba(0, 0, 0, 1);
transform: scale(1.1);
}
#lightbox-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
display: flex;
justify-content: space-between;
pointer-events: none;
}
.lightbox-prev,
.lightbox-next {
background: rgba(0, 0, 0, 0.7);
color: white;
border: none;
font-size: 30px;
padding: 15px 20px;
cursor: pointer;
border-radius: 50%;
transition: all 0.3s ease;
pointer-events: all;
}
.lightbox-prev:hover,
.lightbox-next:hover {
background: rgba(0, 0, 0, 1);
transform: scale(1.1);
}
.lightbox-prev {
margin-left: 20px;
}
.lightbox-next {
margin-right: 20px;
}
.lightbox-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 20px;
font-size: 16px;
}
.gallery-item img {
cursor: pointer;
transition: transform 0.3s ease;
}
.gallery-item img:hover {
transform: scale(1.05);
}
&lt;/style>
&lt;script>
if (typeof window.galleryData === 'undefined') {
window.galleryData = {};
window.openLightbox = function(galleryId, index, imageSrc) {
console.log('=== OPENING LIGHTBOX ===');
console.log('Gallery ID:', galleryId);
console.log('Clicked image src:', imageSrc);
console.log('Clicked image index:', index);
const currentIndex = parseInt(index);
const modal = document.getElementById(`lightbox-modal-${galleryId}`);
const lightboxImage = document.getElementById(`lightbox-image-${galleryId}`);
const imageCounter = document.getElementById(`image-counter-${galleryId}`);
const gallery = document.querySelector(`[data-gallery-id="${galleryId}"]`);
if (!gallery) {
console.log('ERROR: Gallery element not found for ID:', galleryId);
return;
}
const images = Array.from(gallery.querySelectorAll('.gallery-item img')).map(img => img.src);
console.log('Gallery element found:', gallery);
console.log('All images in this gallery:', images);
console.log('Number of images found:', images.length);
const galleryData = {
currentIndex: currentIndex,
images: images.slice()
};
if (galleryData.currentIndex &lt; 0) galleryData.currentIndex = 0;
if (galleryData.currentIndex >= images.length) galleryData.currentIndex = images.length - 1;
window.galleryData[galleryId] = galleryData;
lightboxImage.src = imageSrc;
imageCounter.textContent = `${galleryData.currentIndex + 1} / ${images.length}`;
modal.style.display = 'block';
document.body.style.overflow = 'hidden';
console.log('Stored gallery data for', galleryId, ':', window.galleryData[galleryId]);
console.log('All gallery data keys:', Object.keys(window.galleryData));
console.log('========================');
};
window.closeLightbox = function(galleryId) {
const modal = document.getElementById(`lightbox-modal-${galleryId}`);
modal.style.display = 'none';
document.body.style.overflow = 'auto';
};
window.changeImage = function(galleryId, direction) {
console.log('=== CHANGING IMAGE ===');
console.log('Gallery ID:', galleryId);
console.log('Direction:', direction);
console.log('All available gallery data keys:', Object.keys(window.galleryData));
if (!window.galleryData[galleryId]) {
console.log('ERROR: No gallery data found for:', galleryId);
console.log('Available gallery data:', window.galleryData);
return;
}
const galleryData = window.galleryData[galleryId];
console.log('Gallery data for', galleryId, ':', galleryData);
console.log('Current index before change:', galleryData.currentIndex);
console.log('Images available for this gallery:', galleryData.images);
console.log('Number of images:', galleryData.images.length);
if (!galleryData.images || galleryData.images.length === 0) {
console.log('ERROR: Gallery data is corrupted for:', galleryId);
return;
}
galleryData.currentIndex += direction;
if (galleryData.currentIndex >= galleryData.images.length) {
galleryData.currentIndex = 0;
} else if (galleryData.currentIndex &lt; 0) {
galleryData.currentIndex = galleryData.images.length - 1;
}
const lightboxImage = document.getElementById(`lightbox-image-${galleryId}`);
const imageCounter = document.getElementById(`image-counter-${galleryId}`);
console.log('New index after change:', galleryData.currentIndex);
console.log('New image src:', galleryData.images[galleryData.currentIndex]);
console.log('Lightbox image element found:', lightboxImage);
console.log('Image counter element found:', imageCounter);
if (lightboxImage &amp;&amp; imageCounter) {
lightboxImage.src = galleryData.images[galleryData.currentIndex];
imageCounter.textContent = `${galleryData.currentIndex + 1} / ${galleryData.images.length}`;
console.log('Image updated successfully to:', galleryData.images[galleryData.currentIndex]);
} else {
console.log('ERROR: Could not find lightbox elements for gallery:', galleryId);
}
console.log('=======================');
};
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' || e.keyCode === 27) {
const openModals = document.querySelectorAll('.lightbox-modal[style*="block"]');
openModals.forEach(modal => {
modal.style.display = 'none';
document.body.style.overflow = 'auto';
});
}
});
document.addEventListener('keydown', function(e) {
const openModal = document.querySelector('.lightbox-modal[style*="block"]');
if (openModal) {
const galleryId = openModal.id.replace('lightbox-modal-', '');
if (e.key === 'ArrowLeft' || e.keyCode === 37) {
window.changeImage(galleryId, -1);
} else if (e.key === 'ArrowRight' || e.keyCode === 39) {
window.changeImage(galleryId, 1);
}
}
});
}
&lt;/script>
&lt;hr>
&lt;h2 id="from-cgi-to-wsgi-the-synchronous-era">From CGI to WSGI: The Synchronous Era&lt;/h2>
&lt;p>In the early days of the web, Python web apps communicated with servers using CGI (Common Gateway Interface). Each incoming request launched a brand-new operating system process, ran the application code, generated a response, and then terminated the process. While simple and portable, this was extremely inefficient—process creation was expensive, memory was wasted, and high-traffic sites quickly became bottlenecked.&lt;/p>
&lt;div class="gallery" data-gallery-id="gallery-1757987062-2">
&lt;div class="gallery-grid">
&lt;div class="gallery-item">
&lt;img src="cgi.png" alt="Gallery image 1" loading="lazy" onclick="openLightbox('gallery-1757987062-2', 0 , 'cgi.png')">
&lt;/div>
&lt;div class="gallery-item">
&lt;img src="cgi-arch.png" alt="Gallery image 2" loading="lazy" onclick="openLightbox('gallery-1757987062-2', 1 , 'cgi-arch.png')">
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div id="lightbox-modal-gallery-1757987062-2" class="lightbox-modal" onclick="closeLightbox('gallery-1757987062-2')">
&lt;div class="lightbox-content" onclick="event.stopPropagation()">
&lt;span class="lightbox-close" onclick="closeLightbox('gallery-1757987062-2')">&amp;times;&lt;/span>
&lt;img id="lightbox-image-gallery-1757987062-2" src="" alt="">
&lt;div class="lightbox-nav">
&lt;button class="lightbox-prev" onclick="changeImage('gallery-1757987062-2', -1)">&amp;#10094;&lt;/button>
&lt;button class="lightbox-next" onclick="changeImage('gallery-1757987062-2', 1)">&amp;#10095;&lt;/button>
&lt;/div>
&lt;div class="lightbox-counter">
&lt;span id="image-counter-gallery-1757987062-2">1 / 2&lt;/span>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;style>
.gallery {
margin: 2rem 0;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
padding: 1rem;
}
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
height: 270px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
}
.gallery-item:hover {
transform: translateY(-5px);
}
.gallery-item img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
display: block;
transition: transform 0.3s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
.gallery-item a {
display: block;
text-decoration: none;
}
&lt;/style>
&lt;style>
.lightbox-modal {
display: none;
position: fixed;
z-index: 10000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
cursor: pointer;
}
.lightbox-content {
position: relative;
margin: auto;
padding: 20px;
width: 90%;
max-width: 1200px;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
cursor: default;
}
.lightbox-close {
position: absolute;
top: 15px;
right: 35px;
color: white;
font-size: 40px;
font-weight: bold;
cursor: pointer;
z-index: 10001;
background: rgba(0, 0, 0, 0.7);
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.lightbox-close:hover {
background: rgba(0, 0, 0, 1);
transform: scale(1.1);
}
#lightbox-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
display: flex;
justify-content: space-between;
pointer-events: none;
}
.lightbox-prev,
.lightbox-next {
background: rgba(0, 0, 0, 0.7);
color: white;
border: none;
font-size: 30px;
padding: 15px 20px;
cursor: pointer;
border-radius: 50%;
transition: all 0.3s ease;
pointer-events: all;
}
.lightbox-prev:hover,
.lightbox-next:hover {
background: rgba(0, 0, 0, 1);
transform: scale(1.1);
}
.lightbox-prev {
margin-left: 20px;
}
.lightbox-next {
margin-right: 20px;
}
.lightbox-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 20px;
font-size: 16px;
}
.gallery-item img {
cursor: pointer;
transition: transform 0.3s ease;
}
.gallery-item img:hover {
transform: scale(1.05);
}
&lt;/style>
&lt;script>
if (typeof window.galleryData === 'undefined') {
window.galleryData = {};
window.openLightbox = function(galleryId, index, imageSrc) {
console.log('=== OPENING LIGHTBOX ===');
console.log('Gallery ID:', galleryId);
console.log('Clicked image src:', imageSrc);
console.log('Clicked image index:', index);
const currentIndex = parseInt(index);
const modal = document.getElementById(`lightbox-modal-${galleryId}`);
const lightboxImage = document.getElementById(`lightbox-image-${galleryId}`);
const imageCounter = document.getElementById(`image-counter-${galleryId}`);
const gallery = document.querySelector(`[data-gallery-id="${galleryId}"]`);
if (!gallery) {
console.log('ERROR: Gallery element not found for ID:', galleryId);
return;
}
const images = Array.from(gallery.querySelectorAll('.gallery-item img')).map(img => img.src);
console.log('Gallery element found:', gallery);
console.log('All images in this gallery:', images);
console.log('Number of images found:', images.length);
const galleryData = {
currentIndex: currentIndex,
images: images.slice()
};
if (galleryData.currentIndex &lt; 0) galleryData.currentIndex = 0;
if (galleryData.currentIndex >= images.length) galleryData.currentIndex = images.length - 1;
window.galleryData[galleryId] = galleryData;
lightboxImage.src = imageSrc;
imageCounter.textContent = `${galleryData.currentIndex + 1} / ${images.length}`;
modal.style.display = 'block';
document.body.style.overflow = 'hidden';
console.log('Stored gallery data for', galleryId, ':', window.galleryData[galleryId]);
console.log('All gallery data keys:', Object.keys(window.galleryData));
console.log('========================');
};
window.closeLightbox = function(galleryId) {
const modal = document.getElementById(`lightbox-modal-${galleryId}`);
modal.style.display = 'none';
document.body.style.overflow = 'auto';
};
window.changeImage = function(galleryId, direction) {
console.log('=== CHANGING IMAGE ===');
console.log('Gallery ID:', galleryId);
console.log('Direction:', direction);
console.log('All available gallery data keys:', Object.keys(window.galleryData));
if (!window.galleryData[galleryId]) {
console.log('ERROR: No gallery data found for:', galleryId);
console.log('Available gallery data:', window.galleryData);
return;
}
const galleryData = window.galleryData[galleryId];
console.log('Gallery data for', galleryId, ':', galleryData);
console.log('Current index before change:', galleryData.currentIndex);
console.log('Images available for this gallery:', galleryData.images);
console.log('Number of images:', galleryData.images.length);
if (!galleryData.images || galleryData.images.length === 0) {
console.log('ERROR: Gallery data is corrupted for:', galleryId);
return;
}
galleryData.currentIndex += direction;
if (galleryData.currentIndex >= galleryData.images.length) {
galleryData.currentIndex = 0;
} else if (galleryData.currentIndex &lt; 0) {
galleryData.currentIndex = galleryData.images.length - 1;
}
const lightboxImage = document.getElementById(`lightbox-image-${galleryId}`);
const imageCounter = document.getElementById(`image-counter-${galleryId}`);
console.log('New index after change:', galleryData.currentIndex);
console.log('New image src:', galleryData.images[galleryData.currentIndex]);
console.log('Lightbox image element found:', lightboxImage);
console.log('Image counter element found:', imageCounter);
if (lightboxImage &amp;&amp; imageCounter) {
lightboxImage.src = galleryData.images[galleryData.currentIndex];
imageCounter.textContent = `${galleryData.currentIndex + 1} / ${galleryData.images.length}`;
console.log('Image updated successfully to:', galleryData.images[galleryData.currentIndex]);
} else {
console.log('ERROR: Could not find lightbox elements for gallery:', galleryId);
}
console.log('=======================');
};
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' || e.keyCode === 27) {
const openModals = document.querySelectorAll('.lightbox-modal[style*="block"]');
openModals.forEach(modal => {
modal.style.display = 'none';
document.body.style.overflow = 'auto';
});
}
});
document.addEventListener('keydown', function(e) {
const openModal = document.querySelector('.lightbox-modal[style*="block"]');
if (openModal) {
const galleryId = openModal.id.replace('lightbox-modal-', '');
if (e.key === 'ArrowLeft' || e.keyCode === 37) {
window.changeImage(galleryId, -1);
} else if (e.key === 'ArrowRight' || e.keyCode === 39) {
window.changeImage(galleryId, 1);
}
}
});
}
&lt;/script>
&lt;p>To improve performance, FastCGI emerged. Instead of spawning a fresh process per request, FastCGI kept long-running worker processes alive and reused them for multiple requests. This reduced overhead, but the ecosystem was fragmented and interoperability across frameworks and servers remained messy.&lt;/p>
&lt;p>Enter WSGI (Web Server Gateway Interface), standardized in &lt;a href="https://peps.python.org/pep-3333/" target="_blank" rel="noopener noreferrer">PEP 3333&lt;/a> . WSGI became the universal contract between web servers (like Gunicorn or uWSGI) and Python frameworks (like Flask, Django, or Pyramid). It brought much-needed stability and consistency:
&lt;div class="custom-list ">
&lt;ul style="list-style: none; padding-left: 1.2em;">
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
Servers could run any WSGI app.
&lt;/li>
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
Frameworks no longer needed to worry about server details.
&lt;/li>
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
A thriving ecosystem of tools, middleware, and deployment patterns flourished.
&lt;/li>
&lt;/ul>
&lt;/div> &lt;/p>
&lt;p>For over a decade, WSGI was the backbone of Python web development, powering the synchronous era of the web.&lt;/p>
&lt;div class="gallery" data-gallery-id="gallery-1757987062-1">
&lt;div class="gallery-grid">
&lt;div class="gallery-item">
&lt;img src="wsgi-arch.png" alt="Gallery image 1" loading="lazy" onclick="openLightbox('gallery-1757987062-1', 0 , 'wsgi-arch.png')">
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div id="lightbox-modal-gallery-1757987062-1" class="lightbox-modal" onclick="closeLightbox('gallery-1757987062-1')">
&lt;div class="lightbox-content" onclick="event.stopPropagation()">
&lt;span class="lightbox-close" onclick="closeLightbox('gallery-1757987062-1')">&amp;times;&lt;/span>
&lt;img id="lightbox-image-gallery-1757987062-1" src="" alt="">
&lt;div class="lightbox-nav">
&lt;button class="lightbox-prev" onclick="changeImage('gallery-1757987062-1', -1)">&amp;#10094;&lt;/button>
&lt;button class="lightbox-next" onclick="changeImage('gallery-1757987062-1', 1)">&amp;#10095;&lt;/button>
&lt;/div>
&lt;div class="lightbox-counter">
&lt;span id="image-counter-gallery-1757987062-1">1 / 1&lt;/span>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;style>
.gallery {
margin: 2rem 0;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
padding: 1rem;
}
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
height: 270px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
}
.gallery-item:hover {
transform: translateY(-5px);
}
.gallery-item img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
display: block;
transition: transform 0.3s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
.gallery-item a {
display: block;
text-decoration: none;
}
&lt;/style>
&lt;style>
.lightbox-modal {
display: none;
position: fixed;
z-index: 10000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
cursor: pointer;
}
.lightbox-content {
position: relative;
margin: auto;
padding: 20px;
width: 90%;
max-width: 1200px;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
cursor: default;
}
.lightbox-close {
position: absolute;
top: 15px;
right: 35px;
color: white;
font-size: 40px;
font-weight: bold;
cursor: pointer;
z-index: 10001;
background: rgba(0, 0, 0, 0.7);
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.lightbox-close:hover {
background: rgba(0, 0, 0, 1);
transform: scale(1.1);
}
#lightbox-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
display: flex;
justify-content: space-between;
pointer-events: none;
}
.lightbox-prev,
.lightbox-next {
background: rgba(0, 0, 0, 0.7);
color: white;
border: none;
font-size: 30px;
padding: 15px 20px;
cursor: pointer;
border-radius: 50%;
transition: all 0.3s ease;
pointer-events: all;
}
.lightbox-prev:hover,
.lightbox-next:hover {
background: rgba(0, 0, 0, 1);
transform: scale(1.1);
}
.lightbox-prev {
margin-left: 20px;
}
.lightbox-next {
margin-right: 20px;
}
.lightbox-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 20px;
font-size: 16px;
}
.gallery-item img {
cursor: pointer;
transition: transform 0.3s ease;
}
.gallery-item img:hover {
transform: scale(1.05);
}
&lt;/style>
&lt;script>
if (typeof window.galleryData === 'undefined') {
window.galleryData = {};
window.openLightbox = function(galleryId, index, imageSrc) {
console.log('=== OPENING LIGHTBOX ===');
console.log('Gallery ID:', galleryId);
console.log('Clicked image src:', imageSrc);
console.log('Clicked image index:', index);
const currentIndex = parseInt(index);
const modal = document.getElementById(`lightbox-modal-${galleryId}`);
const lightboxImage = document.getElementById(`lightbox-image-${galleryId}`);
const imageCounter = document.getElementById(`image-counter-${galleryId}`);
const gallery = document.querySelector(`[data-gallery-id="${galleryId}"]`);
if (!gallery) {
console.log('ERROR: Gallery element not found for ID:', galleryId);
return;
}
const images = Array.from(gallery.querySelectorAll('.gallery-item img')).map(img => img.src);
console.log('Gallery element found:', gallery);
console.log('All images in this gallery:', images);
console.log('Number of images found:', images.length);
const galleryData = {
currentIndex: currentIndex,
images: images.slice()
};
if (galleryData.currentIndex &lt; 0) galleryData.currentIndex = 0;
if (galleryData.currentIndex >= images.length) galleryData.currentIndex = images.length - 1;
window.galleryData[galleryId] = galleryData;
lightboxImage.src = imageSrc;
imageCounter.textContent = `${galleryData.currentIndex + 1} / ${images.length}`;
modal.style.display = 'block';
document.body.style.overflow = 'hidden';
console.log('Stored gallery data for', galleryId, ':', window.galleryData[galleryId]);
console.log('All gallery data keys:', Object.keys(window.galleryData));
console.log('========================');
};
window.closeLightbox = function(galleryId) {
const modal = document.getElementById(`lightbox-modal-${galleryId}`);
modal.style.display = 'none';
document.body.style.overflow = 'auto';
};
window.changeImage = function(galleryId, direction) {
console.log('=== CHANGING IMAGE ===');
console.log('Gallery ID:', galleryId);
console.log('Direction:', direction);
console.log('All available gallery data keys:', Object.keys(window.galleryData));
if (!window.galleryData[galleryId]) {
console.log('ERROR: No gallery data found for:', galleryId);
console.log('Available gallery data:', window.galleryData);
return;
}
const galleryData = window.galleryData[galleryId];
console.log('Gallery data for', galleryId, ':', galleryData);
console.log('Current index before change:', galleryData.currentIndex);
console.log('Images available for this gallery:', galleryData.images);
console.log('Number of images:', galleryData.images.length);
if (!galleryData.images || galleryData.images.length === 0) {
console.log('ERROR: Gallery data is corrupted for:', galleryId);
return;
}
galleryData.currentIndex += direction;
if (galleryData.currentIndex >= galleryData.images.length) {
galleryData.currentIndex = 0;
} else if (galleryData.currentIndex &lt; 0) {
galleryData.currentIndex = galleryData.images.length - 1;
}
const lightboxImage = document.getElementById(`lightbox-image-${galleryId}`);
const imageCounter = document.getElementById(`image-counter-${galleryId}`);
console.log('New index after change:', galleryData.currentIndex);
console.log('New image src:', galleryData.images[galleryData.currentIndex]);
console.log('Lightbox image element found:', lightboxImage);
console.log('Image counter element found:', imageCounter);
if (lightboxImage &amp;&amp; imageCounter) {
lightboxImage.src = galleryData.images[galleryData.currentIndex];
imageCounter.textContent = `${galleryData.currentIndex + 1} / ${galleryData.images.length}`;
console.log('Image updated successfully to:', galleryData.images[galleryData.currentIndex]);
} else {
console.log('ERROR: Could not find lightbox elements for gallery:', galleryId);
}
console.log('=======================');
};
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' || e.keyCode === 27) {
const openModals = document.querySelectorAll('.lightbox-modal[style*="block"]');
openModals.forEach(modal => {
modal.style.display = 'none';
document.body.style.overflow = 'auto';
});
}
});
document.addEventListener('keydown', function(e) {
const openModal = document.querySelector('.lightbox-modal[style*="block"]');
if (openModal) {
const galleryId = openModal.id.replace('lightbox-modal-', '');
if (e.key === 'ArrowLeft' || e.keyCode === 37) {
window.changeImage(galleryId, -1);
} else if (e.key === 'ArrowRight' || e.keyCode === 39) {
window.changeImage(galleryId, 1);
}
}
});
}
&lt;/script>
&lt;hr>
&lt;h2 id="the-rise-of-asgi-the-asynchronous-era">The Rise of ASGI: The Asynchronous Era&lt;/h2>
&lt;p>As web applications became more complex and real-time features became essential, the limitations of WSGI became apparent. Modern applications needed:&lt;/p>
&lt;div class="custom-list ">
&lt;ul style="list-style: none; padding-left: 1.2em;">
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
Real-time communication (chat apps, live dashboards).
&lt;/li>
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
WebSockets (persistent two-way connections).
&lt;/li>
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
Streaming responses (sending chunks of data as they’re ready).
&lt;/li>
&lt;li>
&lt;span class="list-marker">✓&lt;/span>
Concurrency at scale (handling thousands of simultaneous users).
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;p>But WSGI is synchronous by design — it expects one request in, one response out. Long-lived or asynchronous tasks block the whole pipeline.&lt;/p>
&lt;p>To fix this, the community created ASGI (Asynchronous Server Gateway Interface). Think of ASGI as the spiritual successor to WSGI, designed for the async era. It’s flexible, supporting multiple protocols (HTTP, WebSocket, MQTT, etc.), and works seamlessly with Python’s asyncio.&lt;/p>
&lt;hr>
&lt;h2 id="key-differences-wsgi-vs-asgi">Key Differences: WSGI vs ASGI&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Feature&lt;/th>
&lt;th>WSGI&lt;/th>
&lt;th>ASGI&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Year Introduced&lt;/strong>&lt;/td>
&lt;td>2003 (PEP 3333)&lt;/td>
&lt;td>2016+ (PEP 484, PEP 800 series)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Nature&lt;/strong>&lt;/td>
&lt;td>Synchronous&lt;/td>
&lt;td>Asynchronous + synchronous&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Protocols&lt;/strong>&lt;/td>
&lt;td>HTTP only&lt;/td>
&lt;td>HTTP, WebSockets, others&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Concurrency&lt;/strong>&lt;/td>
&lt;td>Thread/process-based&lt;/td>
&lt;td>Async I/O (async/await)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Use Cases&lt;/strong>&lt;/td>
&lt;td>CRUD apps, APIs, traditional web&lt;/td>
&lt;td>Realtime apps, streaming, APIs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Examples&lt;/strong>&lt;/td>
&lt;td>Flask, Django (default), Pyramid&lt;/td>
&lt;td>Starlette, FastAPI, Django (channels)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="examples-in-practice">Examples in Practice&lt;/h2>
&lt;h3 id="flask-wsgi">Flask (WSGI)&lt;/h3>
&lt;div style="position: relative;">
&lt;pre>&lt;code>
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, WSGI World!"
&lt;/code>&lt;/pre>
&lt;button class="copy-button" onclick="copyToClipboard(this)"
style="position: absolute; top: 10px; right: 10px; cursor: pointer;">
Copy
&lt;/button>
&lt;/div>
&lt;script>
function copyToClipboard(button) {
const codeBlock = button.previousElementSibling.textContent;
navigator.clipboard.writeText(codeBlock).then(() => {
button.textContent = 'Copied!';
setTimeout(() => { button.textContent = 'Copy'; }, 2000);
});
}
&lt;/script>
&lt;p>Flask handles requests synchronously. Perfect for APIs or apps where requests finish quickly.&lt;/p>
&lt;h3 id="starlette-asgi">Starlette (ASGI)&lt;/h3>
&lt;div style="position: relative;">
&lt;pre>&lt;code>
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
import uvicorn
app = Starlette()
@app.route("/")
async def hello(request):
return PlainTextResponse("Hello, ASGI World!")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
&lt;/code>&lt;/pre>
&lt;button class="copy-button" onclick="copyToClipboard(this)"
style="position: absolute; top: 10px; right: 10px; cursor: pointer;">
Copy
&lt;/button>
&lt;/div>
&lt;script>
function copyToClipboard(button) {
const codeBlock = button.previousElementSibling.textContent;
navigator.clipboard.writeText(codeBlock).then(() => {
button.textContent = 'Copied!';
setTimeout(() => { button.textContent = 'Copy'; }, 2000);
});
}
&lt;/script>
&lt;p>Starlette supports async functions and WebSockets out of the box. Built for highly concurrent, real-time applications.&lt;/p>
&lt;hr>
&lt;h2 id="beyond-wsgi-and-asgi">Beyond WSGI and ASGI&lt;/h2>
&lt;p>While WSGI and ASGI dominate as the two main standards in Python web backends, they are not the whole picture. The web ecosystem is diverse, and not every application fits neatly into the classic request–response model. Let&amp;rsquo;s look at some important alternatives and related technologies.&lt;/p>
&lt;h3 id="1-the-uwsgi-protocol-not-the-same-as-wsgi">1. The uWSGI Protocol (Not the Same as WSGI!)&lt;/h3>
&lt;p>A common source of confusion is between WSGI and uWSGI. Despite the similar names, they are different things:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>WSGI&lt;/strong> is a Python standard (an interface contract between web servers and apps).&lt;/li>
&lt;li>&lt;strong>uWSGI&lt;/strong> is a web server and application container that implements WSGI (and more).&lt;/li>
&lt;/ul>
&lt;p>The uWSGI project also introduced the uWSGI protocol, a high-performance binary protocol for communication between the uWSGI server and upstream servers like Nginx.&lt;/p>
&lt;p>&lt;strong>In other words:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>WSGI = a contract.&lt;/li>
&lt;li>uWSGI = a server + a protocol.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Example workflow:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Your Flask app (WSGI) runs on uWSGI.&lt;/li>
&lt;li>uWSGI speaks the uWSGI protocol with Nginx for speed.&lt;/li>
&lt;li>Nginx handles load balancing and static files.&lt;/li>
&lt;/ol>
&lt;p>This distinction matters because many people mistakenly think &amp;ldquo;uWSGI&amp;rdquo; and &amp;ldquo;WSGI&amp;rdquo; are the same — they&amp;rsquo;re not. One is a standard, the other is an implementation with its own protocol.&lt;/p>
&lt;h3 id="2-graphql-frameworks-ariadne-strawberry-graphene">2. GraphQL Frameworks (Ariadne, Strawberry, Graphene)&lt;/h3>
&lt;p>Traditional REST APIs (built on WSGI or ASGI) follow the request–response cycle: a client asks for a resource (e.g., &lt;code>/users/5&lt;/code>), and the server responds with JSON.&lt;/p>
&lt;p>But GraphQL flips this model:&lt;/p>
&lt;ul>
&lt;li>Clients can specify exactly what data they need.&lt;/li>
&lt;li>The server exposes a schema that describes all possible queries.&lt;/li>
&lt;li>One request can pull related data (e.g., a user and their posts and their comments).&lt;/li>
&lt;/ul>
&lt;p>In Python, frameworks like Ariadne, Strawberry, and Graphene provide GraphQL servers. Under the hood, they still run on WSGI or ASGI servers, but the application model is different.&lt;/p>
&lt;p>👉 &lt;strong>Why it exists:&lt;/strong> REST is rigid for complex data needs. GraphQL solves overfetching (getting too much data) and underfetching (having to call multiple endpoints).&lt;/p>
&lt;p>&lt;strong>Example:&lt;/strong> Instead of&lt;/p>
&lt;pre tabindex="0">&lt;code>GET /user/5
GET /user/5/posts
GET /user/5/comments
&lt;/code>&lt;/pre>&lt;p>you can do:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-graphql" data-lang="graphql">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> user(id: &lt;span style="color:#a6e22e">5&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> posts {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> comments {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> body
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-rpc-and-grpc">3. RPC and gRPC&lt;/h3>
&lt;p>Sometimes, you don&amp;rsquo;t even want &amp;ldquo;web-style&amp;rdquo; APIs at all. You want remote procedure calls (RPC): a client calls a function directly on a server, as if it were local.&lt;/p>
&lt;ul>
&lt;li>Classic RPC frameworks allow you to define service interfaces and expose them to clients.&lt;/li>
&lt;li>&lt;strong>gRPC&lt;/strong> (from Google) is a modern, high-performance RPC framework. It uses Protocol Buffers (Protobuf) for serialization (smaller and faster than JSON).&lt;/li>
&lt;/ul>
&lt;p>With gRPC, you define services like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-protobuf" data-lang="protobuf">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">service&lt;/span> UserService {&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> &lt;span style="color:#66d9ef">rpc&lt;/span> GetUser (UserRequest) &lt;span style="color:#66d9ef">returns&lt;/span> (UserResponse);&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>}&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The client then calls &lt;code>GetUser()&lt;/code> as if it were a local function.&lt;/p>
&lt;p>In Python, you can implement gRPC servers and clients using the official &lt;code>grpcio&lt;/code> library.&lt;/p>
&lt;p>👉 &lt;strong>Why it exists:&lt;/strong> gRPC is popular in microservices architectures because it&amp;rsquo;s:&lt;/p>
&lt;ul>
&lt;li>Efficient (binary serialization, HTTP/2).&lt;/li>
&lt;li>Strongly typed (Protobuf schemas).&lt;/li>
&lt;li>Great for inter-service communication (machine-to-machine).&lt;/li>
&lt;/ul>
&lt;p>While REST/GraphQL are more human-friendly, gRPC is machine-friendly.&lt;/p>
&lt;h3 id="4-serverless-and-function-as-a-service">4. Serverless and Function-as-a-Service&lt;/h3>
&lt;p>Modern deployment models have introduced new ways to think about web applications:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>AWS Lambda, Google Cloud Functions, Azure Functions&lt;/strong>: Run code without managing servers.&lt;/li>
&lt;li>&lt;strong>Vercel, Netlify Functions&lt;/strong>: Deploy Python functions that respond to HTTP events.&lt;/li>
&lt;li>&lt;strong>Serverless frameworks&lt;/strong>: Tools like Zappa and Serverless Framework that package WSGI/ASGI apps for serverless deployment.&lt;/li>
&lt;/ul>
&lt;p>These platforms often abstract away the WSGI/ASGI layer entirely, focusing on event-driven execution.&lt;/p>
&lt;h3 id="5-webassembly-wasm-and-python">5. WebAssembly (WASM) and Python&lt;/h3>
&lt;p>An emerging trend is running Python in the browser through WebAssembly:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Pyodide&lt;/strong>: Runs Python in the browser using WebAssembly.&lt;/li>
&lt;li>&lt;strong>PyScript&lt;/strong>: Allows you to write Python code directly in HTML.&lt;/li>
&lt;li>&lt;strong>Brython&lt;/strong>: A Python 3 implementation for client-side web programming.&lt;/li>
&lt;/ul>
&lt;p>While not directly related to WSGI/ASGI, these technologies represent another evolution in how Python can be used for web development.&lt;/p>
&lt;hr>
&lt;h2 id="putting-it-together">Putting It Together&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>WSGI/ASGI&lt;/strong> → General-purpose web backends, focused on HTTP.&lt;/li>
&lt;li>&lt;strong>uWSGI protocol&lt;/strong> → A server-side optimization for deploying Python apps (not a standard).&lt;/li>
&lt;li>&lt;strong>GraphQL frameworks&lt;/strong> → A new application model for APIs where clients control the data shape.&lt;/li>
&lt;li>&lt;strong>RPC/gRPC&lt;/strong> → A different communication paradigm (function calls instead of HTTP requests), great for microservices.&lt;/li>
&lt;li>&lt;strong>Serverless&lt;/strong> → Event-driven execution without server management.&lt;/li>
&lt;li>&lt;strong>WebAssembly&lt;/strong> → Running Python in the browser.&lt;/li>
&lt;/ul>
&lt;p>Each of these exists because different problems need different tools. A chat app, a scientific microservice cluster, and a public API all have very different requirements — and Python&amp;rsquo;s ecosystem has grown to support them all.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The evolution from CGI to WSGI to ASGI represents Python&amp;rsquo;s adaptation to changing web requirements. WSGI brought standardization and stability to the synchronous web era, while ASGI opened the door to real-time, high-concurrency applications.&lt;/p>
&lt;p>Understanding these protocols helps you:&lt;/p>
&lt;ul>
&lt;li>Choose the right framework for your project.&lt;/li>
&lt;li>Optimize your application&amp;rsquo;s performance.&lt;/li>
&lt;li>Make informed decisions about deployment and scaling.&lt;/li>
&lt;li>Navigate the broader Python web ecosystem.&lt;/li>
&lt;/ul>
&lt;p>Whether you&amp;rsquo;re building a simple REST API with Flask, a real-time chat application with FastAPI, or a microservice with gRPC, Python&amp;rsquo;s web ecosystem has the tools you need. The key is understanding which tool fits your specific use case.&lt;/p>
&lt;hr></description></item></channel></rss>