<rss version="2.0">
  <channel>
    <title>Meet Gor - Tag: python</title>
    <link>https://www.meetgor.com</link>
    <description>Posts tagged with python</description>
    <language>en-us</language>
    <pubDate>Sat, 09 May 2026 05:38:52 UTC</pubDate>
    <item>
      <title>Flight Observatory Mumbai Airspace Case Study</title>
      <link>https://www.meetgor.com/posts/flight-observatory-mumbai-airspace-case-study</link>
      <description>A closer analysis on the airspace around Mumbai&#39;s BOM/VABB (CSMIA) Airport. Questioning the intuitions from memories and answering and reasoning through the data.</description>
      <pubDate>Thu, 02 Apr 2026 00:00:00 UTC</pubDate>
      <content>## Introduction&#xA;&#xA;I do write blogs, newsletters, but this is something wild I had written in a while. I was always curious and interested in knowing about the flights flowing over Mumbai, I do always look up and see the Airline when it appears above my head in front or on the side. But I never actually read or discovered anything true or factual about it. As a software developer, that is very unsettling, I have some ideas and observations but never tested them. I can excuse myself that I am busy and that is a toy little project to get off my hands. But now, we have LLMs and no excuses. I don&#39;t need a second invitation to explore my curiosity, off I went.&#xA;&#xA;And came back with around a week or 2 of digging in data, downloading the ADS-B stream of airspace and crunching the numbers for the BOM (VABB) Mumbai&#39;s Chatrapati Shivaji International Airport (CSMIA) and its vicinity. The numbers I must tell you were staggering and I couldn&#39;t stop starring at them, I felt relieved and felt my childhood was worth while.&#xA;&#xA;I am writing this blog post to explain to myself, how I got here. It was a chaos in start, total misery of data. But after a few attempts of successfully accumulating the data only for BOM airport&#39;s vicinity of 300 kilometers, I was able to finally make sense of the pile of data (since at one point it looked like a goble of mess).&#xA;&#xA;This will be a two part blog. This one being non-technical (that is no grilling from peer developers) which will explain the why and what of the case study of the Mumbai Airport. The next part (out in a couple of days from now) will be a treat for developers (or curse words for myself, ok it won&#39;t be that bad), which will explain the how. &#xA;&#xA;This part is about my intuitions and the memories I had since growing up near the vicinity of Mumbai Airport. I explain what dataset I used (it was not a simple available dataset in the public, mind you that), how I structured it, what questions I asked, how I formed the queries (not in terms of SQL but human logical terms), and how I reason through the results. And trust me everything made sense.&#xA;&#xA;So, if you have been in Mumbai, and wondered how many planes flew above your head in a day, you are in for a treat! Buckle up! Because Mumbai Airport never stands still.&#xA;&#xA;Check out the full case study here -&gt; [Flight Observatory - Mumbai Airspace Case Study](https://dev.meetgor.com/flight-observatory/case-study/mumbai-airport/)&#xA;&#xA;## Flashback of Mumbai from 2010-2018&#xA;&#xA;I have spent my life in Mumbai, the two decades that I have lived here, I often feel nostalgic lookin back at the memories. The one that stands out even today is the plane spotting. Since childhood, I have seen, heard, observed planes flying above me, beyond the buildings, buzzing and slashing the concrete jungles and flat blue slums. I distinctly remember the days from my school, the 7th A classroom, located on the top of the school, the highest room on the school. I remember the days I shared sighting planes through the periods with my friends.&#xA;&#xA;It was a nostalgic experience to see &#xA;- the regular &#34;Air India&#34; on time, &#xA;- &#34;Lufthansa&#34; with its guzzling sound, &#xA;- occasional &#34;UPS&#34; with the jumbo cargo,&#xA;- and the millions of &#34;Jet Air&#34; and &#34;Spicejet&#34; among them. &#xA;&#xA;There was a different joy of just seeing and observing them, admiring the beauty of Boeing 787, 777 and 747s, Airbus A330 and occasional very rare Airbus A380 the king. &#xA;Those are fresh in my mind. I was lucky to be honest to have an airplane enthusiastic friends with me. One of them was notoriously a die-hard fan of British Airways though. The 11:45 British Airways was a treat to him, with me cursing words at British people, hoping the plane to crash (just teasing him of course). &#xA;&#xA;We almost had a anticipation on the next plane to come and over the year we had a schedule that which one will come next depending on the period at the day.&#xA;We had formulated that the BOM Airport&#39;s frequency was 2:30 minutes (that&#39;s 24 flights in an hour, 576 in a day). That is a lot of flights landing over our head, I used to brag that BOM was (is?) the single runway busiest airport. And take a strange pride in defaming British Airways. Those were the times :)&#xA;&#xA;And now, I don&#39;t have that luxury to sit with that friend and gaze at the sky (if I will, I might get existential crisis I think). I wanted to get those moments back, I don&#39;t have the luxury to sit with that friend though, but I realize I have another friend a tool of sorts. LLMs.&#xA;&#xA;I realize I am a programmer now, whatever I want to do, I can. Its just a wish away.&#xA;&#xA;## The Idea&#xA;&#xA;And I began making this project. To observe the sky. Not by my eyes. By tracking, analyzing and crunching data from APIs.&#xA;&#xA;Nothing comes for free. Constraints start developing, but there is no engineer who has no constraints. For me I didn&#39;t want to setup a cloud infrastructure(not that I can&#39;t I just don&#39;t have the budget for it) and do bunch of credit card swipes, I didn&#39;t have the luxury for that, and for buying heavy 200$ subscription of those AI-tools either. So what? &#xA;&#xA;&gt; Engineers aren&#39;t made from freedom. They&#39;re shaped by constraints.&#xA;&#xA;I explored every API out there to see what I can use the best. And lo and behold I found some. Not everything was great, there were samples, just a day for a month, limited data, but I kept digging and digging. I found what I needed, data points for Mumbai (or any airport for that matter) across a decade. I thought I might get full-day, full-year data, a decade worth of data. But what I got was a decade worth of data, for all months, but only for the first day of the month. Sigh!&#xA;&#xA;But I kept moving. I don&#39;t care how little the data is. It is real. It is 12 days of the year for 8 years. That is not a lot, but enough to find patterns. And I can surely say, Mumbai airport hasn&#39;t changed drastically over the decade.&#xA;&#xA;I thought that was not enough! I wanted more, I wanted live data. Can I get live? I though I could, I did but sort of. Its not the most accurate representation of flights. But a good guess, a practical one at least.&#xA;&#xA;So, I built [Flight Observatory](https://dev.meetgor.com/flight-observatory). A live and historical data analysis tool or a webapp of sorts for viewing flights captured by the ADS-Bs around the world. I kept storing the live data and gobbled them into a historical section. From the other dataset and some live observations(which turned out to be a disappointment) I also made a case study for Mumbai Airport.&#xA;&#xA;This will be not quite nerdy and technical, even if you are not a technical person, strap on. Its going to be a fun read.&#xA;The next part will be a treat for the technical people reading this, so put on your glazing glasses and bickering comments for that post later.&#xA;&#xA;## What I found (TLDR)&#xA;&#xA;Mumbai Airspace is very consistent. It is busy in a very Mumbai way: a lot of movement, but with a clear pattern. The traffic pattern is strongest in the early morning and evening. &#xA;&#xA;The daily activity spreads across the early morning, the workday, and the evening. In the airport-proximate analysis:&#xA;- The strongest hourly movement shows up around 07:00 IST&#xA;- Other heavy hours around 06:00, 11:00, 17:00, 20:00, 21:00, and 22:00.&#xA;&#xA;On the sampled days, the data produced:&#xA;- `21,923` airport-proximate events&#xA;- Split into:&#xA;    - `12,723` landings &#xA;    - `9,200` takeoffs &#xA;- With a median landing-to-next-takeoff gap of `1.5` minutes. &#xA;&#xA;That last number matters. It is the airport&#39;s strength and the health checkup number. It says the airport is turning movements over very quickly, which matches the reputation Mumbai has for being tightly packed.&#xA;&#xA;The airspace itself is occupied for a while:&#xA;- Median landing run: `34.5 minutes`&#xA;- Median takeoff run: `25.1 minutes`&#xA;&#xA;So landings stay visible longer than takeoffs. That makes sense operationally, arrivals spend more time in descent and approach, while departures clear out faster once they lift off.&#xA;&#xA;The same aircraft also comes back around fairly quickly:&#xA;- median same-aircraft ground time: `96.5 minutes`&#xA;That means the aircraft are not sitting idle for long. The fleet is being reused in a steady loop, which fits a high-frequency airport like Mumbai.&#xA;&#xA;And the shape of the traffic is not random:&#xA;- Saturday is the busiest weekday in the sample&#xA;- the landing and takeoff corridors split cleanly across the airport area&#xA;- the dominant heading clusters show the same directional axis being used again and again&#xA;&#xA;Across the full analysis set, I ended up with about&#xA;- ~17 million observations (that looks like a gigantic lot, but it isn&#39;t)&#xA;- ~151,000 inferred flight runs&#xA;- Spread across ~4,900 aircraft hexes&#xA;- ~4,800 registrations&#xA;- ~140 aircraft types.&#xA;&#xA;That feels right for Mumbai. It is a city that never really stops moving (it actually sleeps, but internationally awake), and the airspace behaves the same way. The airport-proximate movement counts are also not tiny, it is structured, dense, and repetitive. The airport works like a tight system, not a loose one.&#xA;&#xA;## Notes and Clarifications&#xA;&#xA;The data itself was not clean in the neat, airline-report sense. It is raw observation data: points every few seconds, over and over again, with altitude,&#xA; speed, heading, position, and callsign changing as aircraft move across the sky every 5 seconds or 30 seconds(depending on the year). I had to group those observations into inferred flight runs before it became useful. Once I did that, the picture got much clearer. &#xA;&#xA;Most of the useful traffic is post-covid traffic too. The analysis is heavily weighted toward 2022 to 2025. &#xA;&#xA;&gt; The ADS-B for the BOM (VABB) actually started the tracking of airports around late 2017, early 2018, and that too was per minute. So it was very sparse and not necessarily capturing the full spectrum.&#xA;&gt; This data is from 2018 to 2026, not the full day of each month. It is sampled to be the first full day of each month (i.e. 1st April 2024, 1st of May 2024, and so on). Also the data points from 2018 to mid-2020 is snapshot of 60 seconds and moving from late 2020 through 2026 (till march) is snapshot of 5 seconds.&#xA;&gt; The data is only for ~78 days(not continuous, its the 1st day of month). 2022-2025 full years (12 days), there are only 3 days in 2018, since ADS-B was active after mid-2018, and the dip in 2020-21 due to COVID, so little or no traffic observations.&#xA;```&#xA;----------------------------&#xA;|  Year  |  Number of days |&#xA;---------------------------|&#xA;|  2018  |      3          |&#xA;|  2019  |      11         |&#xA;|  2020  |      5          |&#xA;|  2021  |      9          |&#xA;|  2022  |      12         |&#xA;|  2023  |      12         |&#xA;|  2024  |      12         |&#xA;|  2025  |      12         |&#xA;|  2026  |      3          |&#xA;----------------------------&#xA;```&#xA;&#xA;&gt; The numbers are real, but they are still a proxy. They are not ATC logs. They are not airport ops records. They are ADS-B-derived movement patterns around Mumbai. That means the data is powerful for showing pattern, density, clustering, and dominance, but it should not be mistaken for official runway statistics. This data doesn&#39;t infer the activity the airport is doing exactly as in an official ATC sense, but it does tell you something real about how packed and continuous the traffic feels once you look at it through ADS-B data.&#xA;&#xA;&gt; ADS-B short for Automatic Dependent Surveillance Broadcast that allows the aircraft to capture its own position and other details like (altitude, GPS based location, direction, speed, hex code i.e. unique aircraft code) and broadcasts (sends in the open as a radio signal), this gets captured by listening Air Traffic Controllers(ATC), Airports, satellites, networks, etc which then track and use it for internal purposes. That could be then used for tracking on the Airport, communnicating, averting accidents, etc.&#xA;&#xA;&#xA;## Initial Questions and Exploration&#xA;&#xA;I was looking for a historical dataset that could show me flight activity over Mumbai. I did not expect it to be perfect, but I did expect it to be workable. The first thing to understand is that this is not a clean flight log. It is a stream of observations. Each aircraft keeps broadcasting its position, altitude, speed, heading, and identity over time. That means the raw data is not `one row per flight`. It is more like `one row per movement in time for a given aircraft`. To make it useful, I had to turn those repeated observations into inferred flight runs. That is what makes the analysis readable. Instead of staring at thousands of raw position points, I can ask more human questions:&#xA;&#xA;- When is Mumbai busiest?&#xA;- Which airlines show up the most?&#xA;- Which aircraft dominate?&#xA;- Where do planes start descending?&#xA;- Where do they take off from?&#xA;- How long is the gap between one landing and the next takeoff?&#xA;&#xA;That shift from raw observations to inferred runs is what made the rest of the project possible.&#xA;I landed on the ADS-B Exchange historical data because it looked promising. At first glance, it seemed like I had found a long historical archive. But there was a catch I did not fully appreciate at the beginning: much of the data was sampled by month, and a lot of it was only the first day of each month. That was smaller than I hoped, but still useful enough to extract patterns from.&#xA;&#xA;That was the point where I stopped thinking about the data as a raw archive and started thinking about it as something I could actually analyze.&#xA;&#xA;## Making &#34;sense&#34; of the data &#xA;&#xA;So, I had millions of observations. Because a single minute has 12 snapshots and if you do the math (not asking you to do it, but if you are curious), we end up with around 17,280 snapshot for a single day (12 snapshots per minute * 60 minutes * 24 hours). If you stack it up for around 12 months, that really grows big.&#xA;&#xA;So, what exactly were these snapshots? Could I directly use them in querying the database?&#xA;&#xA;No, not quite. It actually depends on what questions we might need answering.&#xA;&#xA;&#xA;### What is a Snapshot here&#xA;&#xA;A snapshot is basically a list of observations (or set of status) for the aircrafts currently in the airspace. The initial snapshot that I download from [ADS-B Exchange](https://samples.adsbexchange.com/index.html#readsb-hist) has a full world air-traffic and I never want that full data, I just want the tiny airspace around the Mumbai one. So we filter the latitude and longitude position around Mumbai. So, each snapshot finally becomes the aircrafts flying around the radius (30km to give a number) at that time. So we take those kind of snapshots at 5 second interval.&#xA;&#xA;So let&#39;s suppose there are 2 flights around Mumbai currently one landing and one taking off:&#xA;&#xA;These will be a snapshot.&#xA;```json&#xA;[&#xA;    {&#xA;        &#34;icao24&#34;: &#34;123456&#34;,&#xA;        &#34;latitude&#34;: &#34;19.123&#34;,&#xA;        &#34;longitude&#34;: &#34;73.123&#34;,&#xA;        &#34;altitude&#34;: 500,&#xA;        &#34;velocity&#34;: -100,&#xA;        &#34;heading&#34;: 90,&#xA;        &#34;timestamp&#34;: &#34;2022-01-01T00:00:00Z&#34;&#xA;    },&#xA;    {&#xA;        &#34;icao24&#34;: &#34;8901&#34;,&#xA;        &#34;latitude&#34;: &#34;19.083&#34;,&#xA;        &#34;longitude&#34;: &#34;72.123&#34;,&#xA;        &#34;altitude&#34;: 1000,&#xA;        &#34;velocity&#34;: 300,&#xA;        &#34;heading&#34;: 90,&#xA;        &#34;timestamp&#34;: &#34;2022-01-01T00:00:00Z&#34;&#xA;    }&#xA;]&#xA;```&#xA;After 5 seconds you might see a change in the latitude and longitude (positions), the speed, and the altitude (also the timestamp). So we get another snapshot which shows the status of those aircraft currently in the radius of Mumbai, maybe in the next snapshot another aircraft is about to land, so it appears in the snapshot too. So on, you get the point right? If the aircraft stays within the radius of Mumbai for more than a minute (which everyone might, often more than a minute even 10-20 minutes?) then we might have 12 observations for that aircraft across those snapshots. We basically have a record or a progression of the aircraft over time. That is, in essence, a time series data. We are measuring a change of an entity over a period of time.&#xA;&#xA;So, this is what I get from the downloaded snapshots which are in millions.&#xA;&#xA;How do I make sense of them?&#xA;&#xA;Well, let&#39;s ask a question, then the scope can be reduced or we can move in specific direction.&#xA;&#xA;## Questions on the data&#xA;&#xA;&gt; What are the total number of flights over Mumbai in 2026 so far?&#xA;&#xA;Well, now you can see, we are talking about flights or distinct aircraft over a time. That is a distinct thing from the observations we have, we have to first lock in the time, but the deeper question is how do we group these observations to get flights?&#xA;&#xA;That is why the question need to change a little before it can be answered properly. I am asking how many distinct flight runs exist in the data, how long they last, when they start, when they end, and whether they are landing, taking off, or just passing through the Mumbai airspace. Once I do that, the raw snapshots stop being noise and start becoming something I can actually reason about.&#xA;&#xA;So, what constitutes a flight run? That is a question that we need to answer before proceeding.&#xA;&#xA;#### What is a Flight Run&#xA;&#xA;A flight run is my way of saying: this group of observations belongs to the same aircraft movement. It is not one row, and it is not one timestamp. It is the whole stretch of time where the same aircraft keeps showing up in the snapshots and its position, altitude, speed, and heading keep changing in a way that looks like one continuous movement.&#xA;&#xA;Now, how do we distinguish an aircraft?&#xA;&#xA;A unique aircraft means one distinct plane in the dataset, usually identified by its unique aircraft code, like hex or registration.&#xA;&#xA;This matters because the same aircraft can appear in many snapshots while it is in Mumbai&#39;s airspace. If I only counted rows, I would be counting the same aircraft again and again. But if I group those rows into a single run, then I can treat it more like one flight-shaped event. That gives me something much closer to what I actually want to study.&#xA;&#xA;### Observations to Flight Runs&#xA;&#xA;So, I loaded the raw snapshots/observations into the dataset after compressing from json to csv to parquet files (dont worry about the details), into a duck database session.&#xA;&#xA;I had table schema like:&#xA;- `observations` is just all the monthly Parquet files unioned together.&#xA;- `observations_clean` cleans the fields and turns text like altitude, speed, lat/lon, etc. into usable numeric columns.&#xA;- `observations_enriched` adds convenience fields like registration, aircraft type, airline_code, is_indian_registration, etc.&#xA;&#xA;From this we need to group into a flight run that could track each flight. So, I went and created a view that will do that:&#xA;&#xA;As we know we have `hex` in the data for each observations, that can be used to identify unique aircrafts in the airspace, however there is a catch.&#xA;&#xA;Each aircraft in ADS-B data is identified by a unique hex code, which represents the physical plane. But planes don&#39;t operate just one journey, they fly multiple routes throughout the day. This is where the callsign becomes important. A callsign represents the flight the aircraft is currently operating (for example, AI101 or 6E203).&#xA;&#xA;So, we need a combination of `hex` and `callsign` to identify a flight run.&#xA;I sorted the observations by aircraft and time, and then compared each row with the one just before it. If the same aircraft disappeared for too long, I treated that as a break. If the flight code changed, I treated that as a new run too. That gave me a cleaner way to read the data. The same aircraft could still appear many times, but those repeated sightings would stay inside one continuous stretch instead of being counted as separate things. In other words, I was no longer looking at a pile of random snapshots. I was looking at movement. Once that rule was in place, the rest of the questions started making sense. I could count runs instead of rows, compare landings and takeoffs, look at how long an aircraft stayed in the airspace, and see when the traffic was building up or dropping off. That was the point where the data stopped feeling messy and started feeling usable.&#xA;&#xA;That is the first step to answer the question, how many flights are there in Mumbai over 2026?&#xA;&#xA;### How many flights are there in Mumbai over 2026?&#xA;&#xA;Once I started grouping those repeated sightings into flight runs, the question became much clearer. Instead of asking how many rows exist, I could ask how many distinct aircraft movements exist from 1st of January, February and March of 2026. That is the number I actually care about. It tells me how much traffic was visible, when it showed up, and how those movements were spread across the day.&#xA;&#xA;There are approximately `7,237` inferred flight runs for the 3 full days of the 2026.&#xA;&#xA;For the 12 full days of 2024, there were approximately `51,441` inferred flight runs, and for 2025 there were approximately `43,324`. For 2026, it is just the first quarter (3 days compared to 12 days of those earlier years, so a good balance).&#xA;&#xA;Now we can move into interesting questions&#xA;&#xA;### When is Mumbai most busy?&#xA;&#xA;Once I had the raw snapshots grouped into flight runs and filtered down to the airport-proximate ones, the first thing I wanted to see was simple: when does Mumbai actually get busy?&#xA;&#xA;For a &#34;24 hour day&#34; chart, I used:&#xA;- one row per hour&#xA;- count movements for each sampled day + hour&#xA;- then average those hourly counts across sampled days&#xA;&#xA;That gives us a number representing on a typical sampled day, how many movements happen at 07:00, 08:00, etc.?&#xA;&#xA;![Landing Takeoff Hourly](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/landing_takeoff_hourly.png)&#xA;&#xA;The answer is pretty clear. In this sample, the busiest hour is 07:00 IST, with 1,086 movements. Right behind it are 20:00 (1,074), 17:00 (1,073), 18:00 (1,069), 21:00 (1,044), 11:00 (1,043), 19:00 (1,038), and 22:00 (1,032). The quietest hour is 03:00, with just 246 movements. So the traffic is not flat at all. It has a shape, and that shape is very visible once you count the flights as runs instead of raw rows.&#xA;&#xA;However if we take the average across all the sampled days, we get the same picture.&#xA;&#xA;For construcing the query for getting this, we want to know when the airport is most active during the day, so we can:&#xA;- Keeps only airport-proximate events (lat and lon)&#xA;- Labels each event as landing or takeoff (based on speed and altitude)&#xA;- Converts the time to IST&#xA;- Groups by hour of day&#xA;- Counts movements in each hour bucket&#xA;&#xA;![Movement Hourly Average](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/hourly_avg.png)&#xA;&#xA;&#xA;If we take the median instead, then the busiest hours are 17:00 IST, with 30.46 movements, 18:00 IST with 30.54 movements and 07:00 IST with 30.17 movements that makes it very consistent to the average. The quietest hour is still 03:00 with just 7.69 movements.&#xA;&#xA;That gives us a good answer and the difference is honestly not that big. The busiest hour is 07:00 and 17:00 with summed average and median respectively, and quietest is 03:00.&#xA;The summed view and the averaged view tell the same story, but from two slightly different angles. In the total sample, 07:00 is the busiest hour, followed closely by 20:00 and 17:00. In the average-per-day view, the evening shifts slightly ahead, with 17:00 and 18:00 taking the top two spots and 07:00 still staying near the top. The quietest hour is the same in both views: 03:00. So the exact peak changes a little, but the shape does not. Mumbai is still busiest in the morning and evening, and quietest in the late night.&#xA;&#xA;&#xA;### Which Airlines Show Up Most&#xA;&#xA;I want to know which airlines dominate the traffic. &#xA;&#xA;Once the rows were grouped into flight runs, I could stop looking at raw sightings and just count which airline codes kept appearing. I used the first part of the flight code as a rough airline label and counted the runs for each one. That makes the dominant carriers obvious instead of buried inside repeated observations.&#xA;&#xA;What I did was with a query:&#xA;- Takes the grouped flight runs&#xA;- Pulls the airline prefix from the flight code&#xA;- Counts how many runs each prefix appears in&#xA;&#xA;What we get:&#xA;- A dominance chart&#xA;- It shows which carriers keep repeating in the sample&#xA;- IndiGo and Air India should stand out immediately&#xA;&#xA;This was quite evident, as I have observed Mumbai&#39;s sky for a long. Indigo and Air India are firmly dominant across the board. However, there are some other interesting observations like Vistara Air, Akasa and Emirates. In earlier pre-covid era, it used to be Jet Airways, but now Indigo has taken the lead.&#xA;&#xA;The difference in the first 3 is quite a big one, and I think its right! Since Indigo and Air-India, not only in Mumbai, they are preferred over other domestic airlines.&#xA;&#xA;Some other notable ones down the order are `SIA` for Singapore Airlines, `DLH` as Lufthansa, and `BAW` as British Airways. They are not as heavily appearing as domestic ones, they are regulars on a routine, each day every day.&#xA;&#xA;```&#xA;┌──────────────┬───────────┐&#xA;│ airline_code │ movements │&#xA;│   varchar    │   int64   │&#xA;├──────────────┼───────────┤&#xA;│ IGO          │     55050 │&#xA;│ AIC          │     20892 │&#xA;│ VTI          │      9789 │&#xA;│ AKJ          │      6923 │&#xA;│ UAE          │      5057 │&#xA;│ SEJ          │      4869 │&#xA;│ AXB          │      4514 │&#xA;│ QTR          │      3728 │&#xA;│ GOW          │      2191 │&#xA;│ ETD          │      2186 │&#xA;│ IAD          │      2151 │&#xA;│ @@@          │      1708 │&#xA;│ SVA          │      1660 │&#xA;│ ABY          │      1542 │&#xA;│ ETH          │      1504 │&#xA;│ OMA          │      1479 │&#xA;│ SDG          │      1448 │&#xA;│ GFA          │      1214 │&#xA;│ SIA          │       979 │&#xA;│ KAC          │       825 │&#xA;│ DLH          │       771 │&#xA;│ THY          │       754 │&#xA;│ MAS          │       745 │&#xA;│ BAW          │       721 │&#xA;│ FDB          │       705 │&#xA;└──────────────┴───────────┘&#xA;```&#xA;&#xA;![Airline Breakdown](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/airline_routine.png)&#xA;&#xA;A good validation query! It seems the data was correctly analysed.&#xA;&#xA;### Which Aircraft Type Dominates&#xA;&#xA;This was something I didn&#39;t have much idea about. I was not well versed with flight models and their names. So I let it shape what we can find.&#xA;&#xA;```&#xA;┌────────────────────────────────────────────────┬───────────┐ &#xA;│ aircraft_type                                  │ movements │     &#xA;│    varchar                                     │   int64   │&#xA;├────────────────────────────────────────────────┼───────────┤&#xA;│ A20N (Airbus A320neo)                          │     49269 │&#xA;│ A21N (Airbus A321neo)                          │     27739 │&#xA;│ B38M (Boeing 737 MAX 8)                        │     13138 │&#xA;│ A320 (Airbus A320)                             │     12587 │&#xA;│ B77W (Boeing 777-300ER)                        │      7543 │&#xA;│ B738 (Boeing 737-800)                          │      5643 │&#xA;│ B789 (Boeing 787-9 Dreamliner)                 │      3162 │&#xA;│ A321 (Airbus A321)                             │      2761 │&#xA;│ B788 (Boeing 787-8 Dreamliner)                 │      2482 │&#xA;│ B77L (Boeing 777-200LR / 777 freighter family) │      2428 │&#xA;│ A359 (Airbus A350-900)                         │      2385 │&#xA;│ A333 (Airbus A333)                             │      2067 │&#xA;│ AT76 (A319)                                    │      1862 │&#xA;│ A319 (Airbus A319)                             │      1736 │&#xA;│ A388 (Airbus A388)                             │      1648 │&#xA;└────────────────────────────────────────────────┴───────────┘&#xA;```&#xA;&#xA;![Aircraft type breakdown](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/aircraft_types.png)&#xA;&#xA;&#xA;### Where Landings Start&#xA;&#xA;The question here is not where the aircrafts lands on the runway. It is where the aircrafts start coming down. That is a different thing. If I want to find the landing corridor, I should not look for the final stop point. I should look for the first clear movement in each run when the altitude starts dropping in a way that looks like descent. That gives me the start of the landing path, which is what matters for the map.&#xA;&#xA;Once I have those descent-start points, the next step is simple: group them by location and altitude. If the same area keeps showing up, that means aircrafts are repeatedly beginning their descent there. That is how the east-side corridor and the Ghatkopar-area low altitudes start making sense. The map is not showing a single landing dot. It is showing the place where arrival begins to feel like arrival.&#xA;&#xA;The patterns for a sharp dip is observed near the airport only, but overall, the gradual dip is seen on the Navi Mumbai side which is very true. The aircrafts gradually lowering their speeds and altitude descending into the runway strip.&#xA;&#xA;- 19.09,73.02 is east of the airport, out toward the eastern Mumbai / Thane creek / Navi Mumbai corridor.&#xA;- 19.09,73.08 and 19.09,73.10 are farther east, so they sit deeper into the Navi Mumbai / Airoli / Ghansoli / Thane-side approach corridor.&#xA;&#xA;```&#xA;┌────────────┬────────────┬────────────────┬────────────────────┬───────────────┐&#xA;│ lat_bucket │ lon_bucket │ descent_points │     avg_alt_ft     │ median_alt_ft │&#xA;│   double   │   double   │     int64      │       double       │    double     │&#xA;├────────────┼────────────┼────────────────┼────────────────────┼───────────────┤&#xA;│      19.09 │      73.08 │            142 │ 3405.6338028169016 │        3400.0 │&#xA;│      19.09 │       73.1 │            135 │  3652.962962962963 │        3600.0 │&#xA;│      19.09 │      72.86 │            133 │   36.2406015037594 │           0.0 │&#xA;│      19.09 │      73.02 │            123 │  2638.008130081301 │        2625.0 │&#xA;│      18.58 │      73.94 │            122 │ 2115.1639344262294 │        2175.0 │&#xA;│      19.09 │      73.01 │            119 │  2439.285714285714 │        2425.0 │&#xA;│      19.09 │      73.11 │            118 │ 3747.4576271186443 │        3750.0 │&#xA;│      19.09 │      73.09 │            116 │ 3565.0862068965516 │        3525.0 │&#xA;│      19.09 │      72.94 │            113 │ 1342.9203539823009 │        1350.0 │&#xA;│      19.09 │      73.12 │            113 │ 3951.7699115044247 │        3925.0 │&#xA;│      19.09 │      73.23 │            108 │ 5678.7037037037035 │        5675.0 │&#xA;│      19.09 │      73.24 │            102 │  5867.156862745098 │        5850.0 │&#xA;│      19.09 │      73.13 │            101 │  4157.425742574257 │        4175.0 │&#xA;│      19.09 │      73.07 │             99 │ 3325.5050505050503 │        3325.0 │&#xA;│      19.09 │      73.18 │             97 │  4990.721649484536 │        5025.0 │&#xA;│      19.09 │       73.2 │             97 │  5232.216494845361 │        5175.0 │&#xA;│      19.09 │      73.14 │             96 │  4284.635416666667 │        4275.0 │&#xA;│      19.09 │      72.91 │             94 │  819.4148936170212 │         862.5 │&#xA;│      18.58 │      74.01 │             92 │ 3408.4239130434785 │        3400.0 │&#xA;│      19.09 │       73.0 │             92 │  2288.586956521739 │        2300.0 │&#xA;...&#xA;...&#xA;&#xA;```&#xA;![Landing Density](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/descent_density_east_bw.png)&#xA;&#xA;A clear strip can be seen with the aircrafts descending into the runway strip. Also the dip is more pronounced in the Navi Mumbai / Airoli / Ghansoli / Thane-side approach corridor.&#xA;&#xA;I also looked for a query on the Ghatkopar region, and it was amazing to see that it was so near. Around `19.08, 72.94` is Ghatkopar, and aircrafts have an approximate average of 800 feets. 90% of them are below 1,100 feet, that is crazy low. And that clearly explains why the strip straight to the East-West of the airport line (which some part of Ghatkopar falls into) doesn&#39;t have any tall towers.&#xA;&#xA;```&#xA;┌────────────────┬───────────────────┬───────────────┬────────────┬────────────────────┐&#xA;│ descent_points │    avg_alt_ft     │ median_alt_ft │ p90_alt_ft │     p95_alt_ft     │&#xA;│     int64      │      double       │    double     │   double   │       double       │&#xA;├────────────────┼───────────────────┼───────────────┼────────────┼────────────────────┤&#xA;│            395 │ 859.7544303797469 │         775.0 │     1175.0 │ 1332.4999999999989 │&#xA;└────────────────┴───────────────────┴───────────────┴────────────┴────────────────────┘&#xA;```&#xA;&#xA;Another good metric to verify the dataset is proving right.&#xA;&#xA;### Where Takeoffs Start&#xA;&#xA;Takeoffs are the same idea in reverse. The question is not where the plane ends up in the sky. The question is where it starts climbing away from the airport. So instead of looking for a finished takeoff, I look for the first point in each run where altitude clearly begins to rise. That gives me the start&#xA;  of the departure path. &#xA;&#xA;Then I group those start points by location. If the same area keeps lighting up, that tells me where departures are really getting going. That is why the  takeoff map is tighter and closer to the airport than the landing map. It is not trying to show the whole flight. It is only trying to show the first step out of the airport, which is the part that makes the pattern visible.&#xA;&#xA;The sharp climb starts very close to the airport itself, and that is what the takeoff pattern shows clearly. The aircrafts begin to gain altitude almost immediately after leaving the runway, so the takeoff strip stays tight around the airport side instead of spreading out far into the city.&#xA;&#xA;- `19.09,72.81` and `19.09,72.82` sit just west of the airport and catch the early climb corridor right after liftoff. This is the sea side, beyond the Juhu aerodrome, perfect. `19.09,72.78` to `19.09,72.80` are a little farther out, but they still belong to the same western departure corridor, showing that the climb pattern stays compact near the field.&#xA;- `19.09,72.84`, `19.09,72.85`, `19.09,72.86`, and 19.09,72.87 are even closer to the airport edge, so they sit in the immediate departure strip where the climb becomes visible first.&#xA;- &#xA;```&#xA;┌────────────────────┬───────────────────┬────────────────┬────────────────────┬───────────────┐&#xA;│     lat_bucket     │    lon_bucket     │ takeoff_points │     avg_alt_ft     │ median_alt_ft │&#xA;│       double       │      double       │     int64      │       double       │    double     │&#xA;├────────────────────┼───────────────────┼────────────────┼────────────────────┼───────────────┤&#xA;│              19.09 │             72.81 │           1473 │ 2092.6171079429737 │        2050.0 │&#xA;│              19.09 │ 72.82000000000001 │           1446 │ 1889.3672199170123 │        1825.0 │&#xA;│              19.09 │             72.84 │           1374 │ 1094.5778748180494 │        1075.0 │&#xA;│              19.09 │ 72.85000000000001 │           1329 │  764.8796087283672 │         725.0 │&#xA;│              19.09 │             72.83 │           1208 │ 1518.4395695364237 │        1450.0 │&#xA;│              19.09 │             72.87 │           1079 │  465.8016682113068 │         200.0 │&#xA;│              19.09 │              72.8 │            994 │ 2233.6770623742455 │        2150.0 │&#xA;│              19.09 │             72.86 │            848 │  698.1816037735849 │         400.0 │&#xA;│              19.09 │             72.79 │            538 │  2579.460966542751 │        2525.0 │&#xA;│              19.09 │             72.78 │            486 │  2790.792181069959 │        2750.0 │&#xA;│              19.09 │             72.77 │            418 │ 3224.5215311004786 │        3000.0 │&#xA;│              19.09 │             72.76 │            331 │ 3665.7099697885196 │        3475.0 │&#xA;│              19.09 │             72.75 │            234 │ 3924.5726495726494 │        3662.5 │&#xA;│              19.09 │             72.88 │            206 │  976.2135922330098 │         200.0 │&#xA;│              19.09 │             72.74 │            137 │  4216.605839416058 │        3875.0 │&#xA;│              19.09 │             72.73 │            129 │  4780.232558139535 │        4925.0 │&#xA;│              19.09 │             72.72 │             68 │  4781.985294117647 │        4837.5 │&#xA;│              19.18 │             72.74 │             62 │  7492.741935483871 │        7325.0 │&#xA;│              19.19 │             72.75 │             62 │  7558.870967741936 │        7525.0 │&#xA;│ 19.080000000000002 │             72.73 │             54 │  5360.185185185185 │        5162.5 │&#xA;...&#xA;...&#xA;```&#xA;![Takeoff Density](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/takeoff_density_west_bw.png)&#xA;&#xA;There is no major analysis here, since it has plain ocean in front of it, we can&#39;t really track the location thereafter. We can just use the direction that we&#39;ll check separately.&#xA;&#xA;### Frequency of landings and takeoffs&#xA;&#xA;I wanted the spacing between landings on their own, and takeoffs on their own. So I grouped the airport events by type, sorted them in time order, and measured the gap between one landing and the next landing, and one takeoff and the next takeoff. The median is the number to trust most here, because a few long gaps pull the average up. &#xA;&#xA;The process was simple:&#xA;- Classify events as landing or takeoff&#xA;- Sort by time inside each sampled day&#xA;- Compare each event to the next event of the same type&#xA;- Measure the minutes between them&#xA;&#xA;![Landing and Takeoff Frequency](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/landing_takeoff_frequency.png)&#xA;&#xA;In this sample, landings are spaced about 2.58 minutes apart at the median, and takeoffs about 3.0 minutes apart.&#xA;&#xA;What can we observe from this?&#xA;- Landings are spaced a bit more tightly than takeoffs&#xA;- Takeoffs have a slightly longer pause between consecutive events&#xA;- The median is the number to trust most here, because a few long gaps pull the average up&#xA;&#xA;This is the answer I have been chasing all along and it truly feels great to see it that close. As children we had observed the landing frequency as around 2 minute 30 seconds, and it couldn&#39;t be more accurate than this. But the great thing about the BOM/VABB Airport is that it still holds true even after a decade. It truly is the single runway busiest airport.&#xA;&#xA;&#xA;### Landing-to-Takeoff Gaps&#xA;&#xA;I want to measure the time between a flight landed and another took-off (not the same aircraft), any flight, like just after a flight landed, how much time it took for another to takeoff. I think it has to be in the range of 1-2 minutes. That&#39;s the status BOM/VABB Airport has to be the busiest single run way airport. Let&#39;s check and validate it with data.&#xA;&#xA;To answer that, I look at the airport events in time order and take each landing as a starting point. Then I find the next takeoff that follows it and&#xA;  measure the time gap between the two. If that gap is small, it means the airport is keeping the movement going very quickly. If it is large, it means there was a longer pause before the next departure. The interesting part is not the average alone, but the shape of the gaps across the sample.&#xA;&#xA;In this sample, that gap is very short most of the time. The median is 1.5 minutes, and the average is about 2.1 minutes, across 5,297 landing-to-takeoff  intervals. Most of the bars are packed into the first few minutes, which tells me the airport is turning movements over very quickly. The long tail is there too, but it is small. So the real story is not &#34;there are a few huge delays&#34; it is that the typical landing is followed by a takeoff almost immediately.&#xA;&#xA;![Landing-to-Takeoff Turnaround Gap](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/turnaround_gap_refresh_clean_horizontal.png)&#xA;&#xA;And another question with intuition validated with data.&#xA;&#xA;If you think about it for a while, the median is 1.5 minutes, if you do the math (60/1.5 = 40 movements / hour). That alligns well with the hourly average. And it even validates the historical peak of Mumbai of 51 movements with 1,036 daily movements, that number is not far.&#xA;&#xA;It is that the airport&#39;s fame comes from how little space there is between operations. The chart makes that visible: most of the time, the next takeoff follows a landing almost immediately, and that is exactly the kind of traffic that built BOM&#39;s reputation.&#xA;&#xA;### Weekday Pattern&#xA;&#xA;This is a bit interesting question, since I honestly had no clue about the answer. I thought every day would have the same pattern. I was wrong.&#xA;&#xA;To get this, it was straight forward.&#xA;I took the airport events I had already classified as landings and takeoffs, grouped them by weekday in Mumbai time, and counted how many movements landed in each bucket. That gives a simple answer to a simple question: which day of the week gets the most airport activity? This question is asking about the breakdown of the flights based on days of the week.&#xA;&#xA;```&#xA;┌─────────────┬──────────────┬──────────┬──────────┬───────────┐&#xA;│ weekday_num │ weekday_name │ landings │ takeoffs │ movements │&#xA;│    int64    │   varchar    │  int128  │  int128  │   int64   │&#xA;├─────────────┼──────────────┼──────────┼──────────┼───────────┤&#xA;│           0 │ Sunday       │     1444 │     1239 │      2683 │&#xA;│           1 │ Monday       │     1383 │      942 │      2325 │&#xA;│           2 │ Tuesday      │     1695 │     1337 │      3032 │&#xA;│           3 │ Wednesday    │     1748 │     1053 │      2801 │&#xA;│           4 │ Thursday     │     1805 │     1236 │      3041 │&#xA;│           5 │ Friday       │     1859 │     1323 │      3182 │&#xA;│           6 │ Saturday     │     2789 │     2070 │      4859 │&#xA;└─────────────┴──────────────┴──────────┴──────────┴───────────┘&#xA;```&#xA;&#xA;![Weekday Takeoff-Landing Flights Breakdown](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/weekday_breakdown.png)&#xA;&#xA;The result is pretty clear in the sample. Saturday stands out as the busiest day by a wide margin, with 4,859 movements, followed by Friday at 3,182 and Thursday at 3,041. Monday is the quietest at 2,325.&#xA;&#xA;&gt; Landings are higher than takeoffs on every weekday, but the weekend spike is the real signal here. So the sample does not look evenly spread across the week, it has a strong Saturday peak and a weaker Monday trough.&#xA;&#xA;I then looked into the dataset and tried to find the count of each day in the sample, since i had around ~70 days of data (1 day * 12 months * 6 years)&#xA;&#xA;- Saturday 12&#xA;- Sunday 11&#xA;- Wednesday 11&#xA;- Friday 11&#xA;- Tuesday 10&#xA;- Thursday 10&#xA;- Monday 8&#xA;&#xA;And that can explain why Saturday is the busiest day. The sample is not evenly distributed, but it is more or less evenly distributed across the calendar.&#xA;&#xA;&#xA;### Hotspots / Direction&#xA;&#xA;The question here is when the aircrafts starts landing or taking off, what direction are they actually pointing? That&#39;s quite obvious right? But I want to identify a deeper pattern here, from where are they coming from and where are they heading towards.&#xA;&#xA;That matters because it tells you whether Mumbai&#39;s traffic is spread randomly across the compass or funneled through one stable corridor. If the airport is using the same flow again and again, the headings should cluster into a small set of directions instead of being evenly scattered.&#xA;&#xA;To get this, we can:&#xA;- take each inferred flight run&#xA;- classify as a landing or takeoff&#xA;- capture the heading at the relevant point in the run.&#xA;&#xA;For landings, it uses the heading at the end of the run.&#xA;For takeoffs, it uses the heading at the start. &#xA;&#xA;![Landing Direction Map](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/landing_direction_map.png)&#xA;&#xA;![Takeoff Direction Map](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/takeoff_direction_map.png)&#xA;&#xA;Those headings are then grouped into direction sectors and counted, which is what produces the direction chart.&#xA;&#xA;![Landing Heading Histogram](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/landing_heading_hist_vertical.png)&#xA;![Takeoff Heading Histogram](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/takeoff_heading_hist_vertical.png)&#xA;&#xA;The result is very strong and very repetitive. Most landings cluster around 270°, with a smaller 90° cluster, and takeoffs are also dominated by 270°. In the heading data, the top landing sector is 270° with 14,316 points, and the top takeoff sector is also 270° with 10,848 points. That tells me the airport is not behaving like a random cloud of movement. It is using the same directional axis again and again, which is exactly why the hotspot map looks like a corridor instead of a scatterplot.&#xA;&#xA;### Time in Airspace&#xA;&#xA;The question here is are the aircrafts just passing through, or do they stay around Mumbai for a while? The idea is simple, if landings take longer than takeoffs, that usually means approach and descent are keeping the aircraft in the airspace longer.&#xA;&#xA;One thing I wanted to know was how long aircraft actually stay around Mumbai once they enter the airport flow.&#xA;&#xA;![Time in Airspace](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/special_time_in_airspace.png)&#xA;&#xA;So I looked at the duration of each inferred run and compared landings, takeoffs, and the full set together. The pattern is pretty clear: landings stay in the airspace longer than takeoffs. The median landing run is 34.5 minutes, while the median takeoff run is 25.1 minutes. Across all airport events, the median is 27.3 minutes. That tells me the traffic is not just passing through fast. A lot of it is lingering long enough for the airspace to feel full, especially on the landing side.&#xA;&#xA;The interesting part here is not just that landings last longer than takeoffs. It is what that tells me about the way Mumbai moves aircraft through its airspace. A landing is not a single movement. It is a long descent, even slower, and then the final stretch into the runway, so it naturally stays visible in the data for longer. Takeoffs are different. Once an aircraft leaves the runway and starts climbing out, it clears the airport zone much faster. &#xA;&#xA;That is why the landing median is 34.5 minutes, while takeoffs sit closer to 25.1 minutes. This does not mean the aircraft is simply flying slower in the sky. It means the arrival side of the airport keeps aircraft in the flow for longer, which is exactly what you would expect at a busy airport like Mumbai. It also hints at sequencing and approach management: arrivals are being held in the pattern of the airport longer than departures, which is the kind of  behavior that makes the airspace feel crowded even before you count the total number of flights.&#xA;&#xA;So the real takeaway is this: Arrivals spend more time inside the airport&#39;s operating envelope, while departures clear out faster. That difference is part of the airport&#39;s style, and it helps explain why the airspace feels full even when the hourly flow looks ordinary on paper.&#xA;&#xA;&#xA;### Aircraft Turnaround Time&#xA;&#xA;&gt; After this aircraft lands, how long does it stay on the ground before it takes off again?&#xA;&#xA;This is a good question, because it gives a deeper look at the activity on the airport. It doesn&#39;t necessarily give much but still a valid metric to observe.&#xA;&#xA;So I matched each landing with the next takeoff for the same aircraft on the same day. That gives a proper aircraft-level ground-time measure, instead of a flight-level handoff.&#xA;&#xA;![Aircraft Turnaround Time](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/special_same_aircraft_ground_gap.png)&#xA;&#xA;What I observed:&#xA;The turnaround time is steady, but not extremely fast. The median ground time is 96.5 minutes, which is about 1 hour 37 minutes. The middle half of the aircraft stays between 77.5 and 130.5 minutes, and the 90th percentile goes up to 185.3 minutes. Most of the pairs sit in the 60-180 minute range, which suggests a working fleet that is being reused at a regular pace rather than left idle for long stretches.&#xA;&#xA;That also hints at the kind of airport Mumbai is. This looks much more like a high-frequency rotation airport than a place where aircraft disappear for half a day between uses. A lot of the traffic is probably made up of domestic and short-to-medium-haul operations, where the same aircraft can land, sit for a bit, and then go back out again within a couple of hours. So the takeaway is not just &#34;there is a gap&#34; It is that Mumbai keeps the aircraft moving in a fairly tight loop, which fits the airport&#39;s reputation for being heavily used and constantly in motion.&#xA;&#xA;### International vs Domestic Flights&#xA;&#xA;I also wanted to see how much of Mumbai&#39;s traffic is Indian-registered versus foreign-registered. I could not see the route directly in the ADS-B feed, so I used aircraft registration as a proxy: `VT-` for Indian-registered aircraft and everything else as foreign-registered. That is not the same thing as domestic vs international routes, but it gives a useful high-level split.&#xA;&#xA;```&#xA;┌────────────────────────┬───────────┐&#xA;│         bucket         │ movements │&#xA;│        varchar         │   int64   │&#xA;├────────────────────────┼───────────┤&#xA;│ Unknown                │      3399 │&#xA;│ Indian-registered (VT) │    110492 │&#xA;│ Foreign-registered     │     37589 │&#xA;└────────────────────────┴───────────┘&#xA;```&#xA;&#xA;![Domestic vs International Flight Aircraft Carrier Split](https://meetgor-cdn.pages.dev/flight-observatory-mumbai/special_domestic_international_proxy.png)&#xA;&#xA;The result is clear: about `72.9%` of movements are Indian-registered, while `24.8%` are foreign-registered. &#xA;So the airport is still dominated by Indian traffic by count, but foreign aircraft are not a side note, or even a minority. They are a substantial and regular part of the flow.&#xA;&#xA;&#xA;## What Surprised Me&#xA;&#xA;What surprised me most was not that Mumbai is busy. I already expected that. What surprised me was how tight and consistent the whole system is. The airport does not just have a lot of movement, it has a deadly consistency to it. A landing is followed by a takeoff almost immediately, the same aircraft comes back into service in about an hour and a half, and the busiest hours are not random bursts but repeatable peaks that keep showing up in the same parts of the day.&#xA;&#xA;The other thing that stood out was how directional the traffic is. Once I started looking at landings and takeoffs on the map, the pattern stopped looking like scattered noise and started looking like a corridor. The airport is not using the full sky evenly. It keeps leaning on the same approach and departure axis, which makes the movement feel much more concentrated than a simple flight count would suggest. I mean that is no brainer, since it can disrupt the residential living, the noise and the unpredictable zoning of planes is not airports should be doing. And Mumbai does it very neatly.&#xA;&#xA;I also did not expect the weekday pattern to be so uneven. I assumed the traffic would be roughly similar across the week, but Saturday clearly pulled ahead in the sample. That was a good reminder that airports are not just shaped by the clock, they are also shaped by the calendar. Even with a dataset this limited, the weekly pattern still shows through.&#xA;&#xA;There is the big myth which gets debunked here, that is &#34;Mumbai Airport is busiest in the late night&#34;. Opposite is true here. The airport is busiest in the morning and evening, and quietest in the late night. The airport is not empty for long. Though there might be international flights in the late night but they don&#39;t land and takeoff with the frequency as close as Indigo does, so that Mumbaikars can sleep finally.&#xA;&#xA;Another small but satisfying surprise was that the numbers matched the memory. As a child, I had the feeling that Mumbai&#39;s sky was never empty for long. The data backed that up. It felt satisfying to see the numbers in front of me for the frequency of landing, the takeoff to landing gaps, the dominance of airline, and mapping them to intuition and memories. The frequency is consistent like crazy, the turnaround period really tight, people do have a favorite preference of Indigo, everything just feels calm but its really quick and consistent. The airport really does stand up to its name &#34;Single Runway Busiest Airport&#34;.&#xA;&#xA;## Conclusion&#xA;&#xA;Well! That was a lot I admit. But it was fun. Atleast to me (I had said, it was a post for me!). I hope you got a little smile out of it if not just felt good reading about how Mumbai&#39;s Airport Operate.&#xA;This is still ADS-B data, so it is a proxy, not an official operational log. But for the question I started with, it is enough. The feeling I had as a kid was real, Mumbai&#39;s sky was never empty for long. The frequency of landing is truly at 2 minute 30 second roughly speaking, which just makes me feel joyous, the airport turnaround time is quite a sharp and tight one, making it really a Mumbai-like busy runway.&#xA;But that is just part 1 buddy! Haven&#39;t nerded about the complications of ADS-B data and my struggles of it, fighting with the limited disk space to compress the data and juggernauting queries to get results. That is the next part.&#xA;&#xA;&#xA;Check out the full case study here -&gt; [Flight Observatory - Mumbai Airspace Case Study](https://dev.meetgor.com/flight-observatory/case-study/mumbai-airport/)</content>
      <type>posts</type>
    </item>
    <item>
      <title>Printf Debugging in Go with Q</title>
      <link>https://www.meetgor.com/links/q-but-for-go-printf-debugging</link>
      <description>This is cool, we make logging a mess. For logs we need to have separate scripts to get relevant data. How much chaos it can be.</description>
      <pubDate>Thu, 08 Jan 2026 00:00:00 UTC</pubDate>
      <content>This is cool, we make logging a mess. For logs we need to have separate scripts to get relevant data. How much chaos it can be.</content>
      <type>links</type>
    </item>
    <item>
      <title>Gotcha with Chained Assignment in Python</title>
      <link>https://www.meetgor.com/til/python-chain-assignment-gotcha</link>
      <description>A lesson learned about Python&#39;s chained assignment with mutable objects, where all variables store references to the same object, leading to unexpected behaviour when one is modified.</description>
      <pubDate>Wed, 27 Nov 2024 00:00:00 UTC</pubDate>
      <content>I was writing some Python code and wanted to initialize a few variables to an empty list. Instead of creating separate lists for each variable, I decided to use chained assignments like this:&#xA;&#xA;```python&#xA;a = b = c = [1,2,3]&#xA;```&#xA;&#xA;It seems okay, nothing new, we are just assigning a, b, and c as an empty list. But notice the problem if you can.&#xA;&#xA;If we try to change the value of b, what do you think is going to happen?&#xA;&#xA;```python&#xA;b.append(4)&#xA;```&#xA;&#xA;If you didn’t know about how Python handles assignments, you might expect the variables to behave like this:&#xA;&#xA;* `a` should still be `[1, 2, 3]`&#xA;    &#xA;* `b` should be `[1, 2, 3, 4]` (since I modified b)&#xA;    &#xA;* `c` should remain `[1, 2, 3]`&#xA;    &#xA;&#xA;```python&#xA;# You might think it will be this&#xA;# a = [1,2,3]&#xA;# b = [1,2,3,4]&#xA;# c = [1,2,3]&#xA;```&#xA;&#xA;But, hello python, it hides the pointer magic behind this statement&#xA;&#xA;The actual and expected state of the variables is:&#xA;&#xA;```python&#xA;# But it is actually this&#xA;# a = [1,2,3,4]&#xA;# b = [1,2,3,4]&#xA;# c = [1,2,3,4]&#xA;```&#xA;&#xA;When you chain the assignment like this, all three variables refer to the same list object in memory. This means, that all the variables will hold the same object, this means that if you change any of the variables assigned that reference it will change the object, and that will result in changing all the variables since they are referring to the same variable.&#xA;&#xA;You are not creating three independent lists. Instead, all three variables (a, b, and c) are referencing the same list object in memory. They don’t hold copies of the list, they all point to the same object. In Python, this is called **reference assignment**.&#xA;&#xA;When you modify one of the variables (like appending to b), you’re not modifying just b, you’re modifying the single list object that all three variables are referencing. Since all three variables point to the same list, any change you make to the list will be reflected in all three variables.&#xA;&#xA;On the other hand, if I do the same thing with strings, like this:&#xA;&#xA;```python&#xA;a = b = c = &#34;hello&#34;&#xA;b = &#34;world&#34;&#xA;&#xA;# a = &#34;hello&#34;&#xA;# b = &#34;world&#34;&#xA;# c = &#34;hello&#34;&#xA;```&#xA;&#xA;This only mutates the b variable and not the a and c, since string is not a mutable object in Python.&#xA;&#xA;In Python, the objects are either [mutable or immutable](https://realpython.com/python-mutable-vs-immutable-types/)&#xA;&#xA;Some of the primitive data types are:&#xA;&#xA;Mutable Types:&#xA;&#xA;* List&#xA;    &#xA;* Dictionaries&#xA;    &#xA;* Set&#xA;    &#xA;* Byte Array&#xA;    &#xA;&#xA;Immutable Types:&#xA;&#xA;* Integer, Float, Complex&#xA;    &#xA;* String&#xA;    &#xA;* Tuple&#xA;    &#xA;* Bytes&#xA;    &#xA;* Boolean&#xA;    &#xA;* Frozenset&#xA;    &#xA;&#xA;So, suppose you assign multiple variables with the same value of a mutable type. In that case, the change in one variable will mutate the other variables as well since the underlying object in memory is the same.&#xA;&#xA;So, this is what I learned from the mistake, avoid the chining assignment when dealing with mutable objects&#xA;&#xA;Instead do the following:&#xA;&#xA;```python&#xA;a, b, c = [], [], []&#xA;b.append(256)&#xA;&#xA;# a = []&#xA;# b = [256]&#xA;# c = []&#xA;```&#xA;&#xA;This is safe and the right way to assign variables to individual values instead of the same value being referred to by all the variables.&#xA;&#xA;Happy Coding :)</content>
      <type>til</type>
    </item>
    <item>
      <title>Turn Python dictionary into a neat CSV table</title>
      <link>https://www.meetgor.com/til/python-dict-to-csv-table</link>
      <description>Exploring how to write python dict/key-value pairs and a table-like structure to a CSV file.</description>
      <pubDate>Wed, 20 Mar 2024 00:00:00 UTC</pubDate>
      <content>## Populating a Python dict having a table-like structure to a CSV&#xA;&#xA;Today, I want to share with you a neat trick I recently discovered for populating a CSV file with data in a table-like structure using Python.&#xA;&#xA;### Writing Key-Value Pairs to a CSV Row&#xA;&#xA;Firstly, let&#39;s discuss the `write_key_value` function. This function allows us to write key-value pairs to a CSV row. It&#39;s particularly useful when dealing with metrics or data that can be represented as simple pairs.&#xA;&#xA;```python&#xA;# Function to populate a CSV row with key-value pairs&#xA;def write_key_value(writer, dictionary):&#xA;    for key, value in dictionary.items():&#xA;        writer.writerow([key, value])&#xA;```&#xA;&#xA;### Writing a Table-Like Structure to a CSV File&#xA;&#xA;Now, let&#39;s dive into the `write_table` function, which handles more complex scenarios where the data follows a table-like structure. This function takes into account different types of metrics and adjusts the CSV table structure accordingly.&#xA;&#xA;Assuming you have a structure of the dictionary like:&#xA;&#xA;```python&#xA;data = {&#xA;    &#34;Students&#34;: {&#xA;        &#34;John Doe&#34;: {&#xA;            &#34;course&#34;: &#34;Mathematics&#34;,&#xA;            &#34;grade&#34;: &#34;A&#34;,&#xA;            &#34;attendance&#34;: 95,&#xA;            &#34;assignments_completed&#34;: 15,&#xA;            &#34;student_id&#34;: &#34;JD001&#34;&#xA;        },&#xA;        &#34;Alice Smith&#34;: {&#xA;            &#34;course&#34;: &#34;Physics&#34;,&#xA;            &#34;grade&#34;: &#34;B+&#34;,&#xA;            &#34;attendance&#34;: 85,&#xA;            &#34;assignments_completed&#34;: 12,&#xA;            &#34;student_id&#34;: &#34;AS002&#34;&#xA;        },&#xA;        &#34;Bob Johnson&#34;: {&#xA;            &#34;course&#34;: &#34;Computer Science&#34;,&#xA;            &#34;grade&#34;: &#34;A-&#34;,&#xA;            &#34;attendance&#34;: 90,&#xA;            &#34;assignments_completed&#34;: 14,&#xA;            &#34;student_id&#34;: &#34;BJ003&#34;&#xA;        }&#xA;    }&#xA;}&#xA;```&#xA;&#xA;And you want to write it to a CSV file, like this:&#xA;&#xA;```csv&#xA;student, course, grade, attendance, assignments_completed, student_id&#xA;John Doe, Mathematics, A, 95, 15, JD001&#xA;Alice Smith, Physics, B+, 85, 12, AS002&#xA;Bob Johnson, Computer Science, A-, 90, 14, BJ003&#xA;```&#xA;&#xA;We can create a function `write_table` that will take in the `dictionary` as the actual data. We want to store the keys of the inner dictionary to be the header/columns of the csv file. As we can see the keys of the inner dict i.e. the value for the key `John Doe` is a dict with the keys `course`, `grade`, `attendance`, etc. which remain the same for the all the keys in the dictionary.&#xA;&#xA;So, we can first create a `row_keys` variable to store the keys of the actual dictionary this will be the first column rows in the csv. &#xA;&#xA;Further we check if the `row_keys` is a dict and then we append it with the `index_key` which will be the first column in the csv. Since all the keys remain the same for the inner-dict, we can pick the first dict and create the `header` with the inner-dict keys.&#xA;&#xA;So, we can write the list `header` to the csv file.&#xA;&#xA;Then for each key in the `row_keys` we can create a list `row` with the key and the values of the inner-dict.&#xA;&#xA;&#xA;```python&#xA;# Function to populate a CSV with a table-like structure&#xA;def write_table(writer, dictionary, index_key):&#xA;&#xA;    row_keys = list(dictionary.keys())&#xA;&#xA;    if row_keys and data[row_keys[0]] is not None:&#xA;        headers = [index_key] + list(&#xA;            dictionary[row_keys[0]].keys()&#xA;        )&#xA;    else:&#xA;        return&#xA;    writer.writerow(headers)&#xA;    for key in row_keys:&#xA;        row = [key] + list(dictionary[key].values())&#xA;        writer.writerow(row)&#xA;&#xA;&#xA;with open(&#39;data.csv&#39;, &#39;w&#39;, newline=&#39;&#39;) as csvfile:&#xA;    writer = csv.writer(csvfile)&#xA;    for key in data:&#xA;        write_table(writer, data[key], key)&#xA;```&#xA;&#xA;### Example Usage&#xA;&#xA;To illustrate how these functions can be used, let&#39;s consider a scenario where we have various types of metrics to populate into a CSV file. We handle key-value paired metrics separately and then populate the CSV with table-like metrics.&#xA;&#xA;```python&#xA;import csv&#xA;&#xA;data = {&#xA;    &#34;Students&#34;: {&#xA;        &#34;John Doe&#34;: {&#xA;            &#34;course&#34;: &#34;Mathematics&#34;,&#xA;            &#34;grade&#34;: &#34;A&#34;,&#xA;            &#34;attendance&#34;: 95,&#xA;            &#34;assignments_completed&#34;: 15,&#xA;            &#34;student_id&#34;: &#34;JD001&#34;&#xA;        },&#xA;        &#34;Alice Smith&#34;: {&#xA;            &#34;course&#34;: &#34;Physics&#34;,&#xA;            &#34;grade&#34;: &#34;B+&#34;,&#xA;            &#34;attendance&#34;: 85,&#xA;            &#34;assignments_completed&#34;: 12,&#xA;            &#34;student_id&#34;: &#34;AS002&#34;&#xA;        },&#xA;        &#34;Bob Johnson&#34;: {&#xA;            &#34;course&#34;: &#34;Computer Science&#34;,&#xA;            &#34;grade&#34;: &#34;A-&#34;,&#xA;            &#34;attendance&#34;: 90,&#xA;            &#34;assignments_completed&#34;: 14,&#xA;            &#34;student_id&#34;: &#34;BJ003&#34;&#xA;        }&#xA;    },&#xA;    &#34;Countries&#34;: {&#xA;        &#34;USA&#34;: {&#xA;            &#34;capital&#34;: &#34;Washington, D.C.&#34;,&#xA;            &#34;population&#34;: 331000000,&#xA;            &#34;area_sq_km&#34;: 9833517,&#xA;            &#34;official_languages&#34;: [&#34;English&#34;, &#34;Spanish&#34;],&#xA;            &#34;currency&#34;: &#34;United States Dollar (USD)&#34;&#xA;        },&#xA;        &#34;India&#34;: {&#xA;            &#34;capital&#34;: &#34;New Delhi&#34;,&#xA;            &#34;population&#34;: 1380004385,&#xA;            &#34;area_sq_km&#34;: 3287263,&#xA;            &#34;official_languages&#34;: [&#34;Hindi&#34;, &#34;English&#34;],&#xA;            &#34;currency&#34;: &#34;Indian Rupee (INR)&#34;&#xA;        },&#xA;        &#34;Brazil&#34;: {&#xA;            &#34;capital&#34;: &#34;Brasília&#34;,&#xA;            &#34;population&#34;: 212559417,&#xA;            &#34;area_sq_km&#34;: 8515770,&#xA;            &#34;official_languages&#34;: [&#34;Portuguese&#34;],&#xA;            &#34;currency&#34;: &#34;Brazilian Real (BRL)&#34;&#xA;        }&#xA;    }&#xA;}&#xA;&#xA;def populate_table(writer, data, index_key):&#xA;    row_keys = list(data.keys())&#xA;    if row_keys and data[row_keys[0]] is not None:&#xA;        headers = [index_key] + list(data[row_keys[0]].keys())&#xA;    else:&#xA;        return&#xA;    writer.writerow(headers)&#xA;    for key in row_keys:&#xA;        row = [key] + list(data[key].values())&#xA;        writer.writerow(row)&#xA;&#xA;&#xA;with open(&#39;data.csv&#39;, &#39;w&#39;, newline=&#39;&#39;) as csvfile:&#xA;    writer = csv.writer(csvfile)&#xA;    for key in data:&#xA;        populate_table(writer, data[key], key)&#xA;        writer.writerow([])&#xA;```&#xA;&#xA;```csv&#xA;Students,course,grade,attendance,assignments_completed,student_id&#xA;John Doe,Mathematics,A,95,15,JD001&#xA;Alice Smith,Physics,B+,85,12,AS002&#xA;Bob Johnson,Computer Science,A-,90,14,BJ003&#xA;&#xA;Countries,capital,population,area_sq_km,official_languages,currency&#xA;USA,&#34;Washington, D.C.&#34;,331000000,9833517,&#34;[&#39;English&#39;, &#39;Spanish&#39;]&#34;,United States Dollar (USD)&#xA;India,New Delhi,1380004385,3287263,&#34;[&#39;Hindi&#39;, &#39;English&#39;]&#34;,Indian Rupee (INR)&#xA;Brazil,Brasília,212559417,8515770,[&#39;Portuguese&#39;],Brazilian Real (BRL)&#xA;```&#xA;&#xA;Thank you, Happy Coding :)</content>
      <type>til</type>
    </item>
    <item>
      <title>Create a Non-Clustered Index in Django with Postgres as DB</title>
      <link>https://www.meetgor.com/til/django-non-clustered-index-pg</link>
      <description>Understanding how to add a non-clustered index in a postgres database in a django project.</description>
      <pubDate>Thu, 10 Nov 2022 00:00:00 UTC</pubDate>
      <content>## What is a non-clustered index?&#xA;&#xA;A non-clustered index is a seperate structure than an actual table in the database, it stores the non-clustered index key(the column which we want to sort in the table), and a pointer to the actual values based on the index key. So, non-clustered indexes do not change the physical order of the table records, instead it holds a structure that can provide a easier and distinct way to fetch objects based on a particular column as the primary key in the structure.&#xA;&#xA;## How to create a non-clustered index in django&#xA;&#xA;In django, we can use the [db_index](https://docs.djangoproject.com/en/4.1/ref/models/indexes/) property on a field(s) to create a index on the table/model. &#xA;&#xA;### Add the property to the field in the model&#xA;&#xA;Chose a field in which, you want to add a index. It can be a foreign key or any other normal field defined in your model.&#xA;&#xA;We have used the typical blog model, so used in the some of my [TILS](https://www.meetgor.com/tils/) in django, it is just convenient to explain and understand as well. We have a django project named `core` and it has a app `blog` with a model defined below. The model `Article` has a few attributes like `title`, `description`, `content` and `status`.&#xA;&#xA;```python&#xA;from django.db import models&#xA;&#xA;ARTICLE_STATUS = [&#xA;    (&#34;PUBLISHED&#34;, &#34;Published&#34;),&#xA;    (&#34;DRAFT&#34;, &#34;Draft&#34;),&#xA;]&#xA;&#xA;class Article(models.Model):&#xA;    title = models.CharField(max_length=128, db_index=True)&#xA;    description = models.CharField(max_length=512)&#xA;    content = models.TextField()&#xA;    status = models.CharField(max_length=16, choices=ARTICLE_STATUS, default=&#34;DRAFT&#34;)&#xA;&#xA;    def __str__(self):&#xA;        return self.title&#xA;```&#xA;&#xA;So, we have added a `db_index` to the title column in the model as a property. This will be equivalent to creating a index in `SQL` as follows:&#xA;&#xA;```&#xA;$ python manage.py makemigrations&#xA;&#xA;Migrations for &#39;blog&#39;:&#xA;  blog/migrations/0002_alter_article_title.py&#xA;    - Alter field title on article&#xA;```&#xA;&#xA;```&#xA;$ python manage.py migrate&#xA;&#xA;Operations to perform:&#xA;  Apply all migrations: admin, auth, blog, contenttypes, sessions&#xA;Running migrations:&#xA;  Applying blog.0002_alter_article_title... OK&#xA;&#xA;```&#xA;&#xA;Indexes are not standard as in SQL, but each vendor(sqlite, postgres, mysql) have their own flavour of syntax and naunces.&#xA;&#xA;```sql&#xA;CREATE INDEX &#34;blog_article_title_3c514952&#34; ON &#34;blog_article&#34; (&#34;title&#34;);&#xA;&#xA;CREATE INDEX &#34;blog_article_title_3c514952_like&#34; ON &#34;blog_article&#34; (&#34;title&#34; varchar_pattern_ops);&#xA;```&#xA;&#xA;The above index commands are specific to the field, as the title field is a varchar, it has two types of index, it can generate one with simple match and other for `LIKE` comparisons because of string comparison behaviour.&#xA;&#xA;So, we just created a simple index and now if we query the db for a particular `title` which now has its own index for the table `blog_article`. This means, we will be able to fetch queries quickly if we are specifically filtering for `title`.&#xA;&#xA;### Adding some data records&#xA;&#xA;We can add a few data records to test the query from the databse, you can ignore this part as it would be just setting up a django project and adding a few records to the databse. This part won&#39;t make sense for people reading to get the actual stuff done, move to the next part please.&#xA;&#xA;```&#xA;python manage.py createsuperuser&#xA;# Create a super user and run the server&#xA;&#xA;python manage.py runserver&#xA;# Locate to http://127.0.0.1:8000/admin&#xA;# Create some records in the artilce model&#xA;```&#xA;&#xA;So, after creating some records, you should have a simple database and a working django application.&#xA;&#xA;```sql&#xA;SELECT * FROM blog_article;&#xA;```&#xA;```&#xA;blog_test=# SELECT * FROM blog_article;&#xA;&#xA; id |  title   | description |          content          |  status   &#xA;----+----------+-------------+---------------------------+-----------&#xA;  1 | test     | test 1      | test content              | DRAFT&#xA;  2 | testpost | test 2      | test content more content | DRAFT&#xA;  3 | newpost  | test 3      | test nothing              | PUBLISHED&#xA;(3 rows)&#xA;```&#xA;&#xA;## Testing Queries&#xA;&#xA;We can now use SQL queries or django filters to check if we get results by a sequential or an index scan. If we have a filter of `title` we will get the results after performing an `Index Scan` which means, it will look up in the index columns rather than scanning the entire table of records. This is a way **we can test the indexes are working, efficiency is a differnet topic.** We can&#39;t get a idea of performance with this little data and just one connection. A real time database and having multiple conncurrent requests and connections is a good environment to test(don&#39;t do it in a production db :)&#xA;&#xA;&#xA;```sql&#xA;EXPLAIN SELECT * FROM blog_article WHERE description LIKE &#39;test 2&#39;;&#xA;```&#xA;&#xA;```&#xA;blog_test=# EXPLAIN ANALYSE SELECT * FROM blog_article WHERE description LIKE &#39;test&#39;;&#xA;---------------------------------------------------------------------------------------------------------&#xA;&#xA; Seq Scan on blog_article  (cost=0.00..11.00 rows=1 width=880) (actual time=0.180..0.181 rows=0 loops=1)&#xA;   Filter: ((description)::text ~~ &#39;test&#39;::text)&#xA;   Rows Removed by Filter: 3&#xA; Planning Time: 0.189 ms&#xA; Execution Time: 0.217 ms&#xA;(5 rows)&#xA;&#xA;```&#xA;&#xA;The above query selects the records whose `description` is like `test 2`, this performs a `Sequenitial Scan` in the database i.e. iterating over the records one by one of the order of the primary key / id of the records in the table. &#xA;&#xA;```sql&#xA;EXPLAIN SELECT * FROM blog_article WHERE title LIKE &#39;test 2&#39;;&#xA;```&#xA;&#xA;```&#xA;blog_test=# EXPLAIN ANALYSE SELECT * FROM blog_article WHERE title LIKE &#39;test&#39;;&#xA;---------------------------------------------------------------------------------------------------------&#xA;&#xA;Index Scan using blog_article_title_3c514952_like on blog_article  (cost=0.14..8.16 rows=1 width=880) (actual time=0.043..0.048 rows=1 loops=1)&#xA;   Index Cond: ((title)::text = &#39;test&#39;::text)&#xA;   Filter: ((title)::text ~~ &#39;test&#39;::text)&#xA; Planning Time: 0.208 ms&#xA; Execution Time: 0.093 ms&#xA;(5 rows)&#xA;```&#xA;&#xA;In the above query, the select statement has a filter with the title being like `test 2`, and since we have a index for looking for like of title column, the database performs a index scan on that table and fetches the result.&#xA;&#xA;Here are some tradeoffs, the planning is more and the execution time is less, this is quite logical as it would take time to make decision because the database has more options than before creating indexes.&#xA;&#xA;In the query where we filtered the description, the planning time was less as it makes sense there was just one option to go for sequential scan, but it took time to perform the operation as it would scan the entire table one by one.&#xA;&#xA;## Using Django to test queries&#xA;&#xA;We can even use django to filter out the objects in the table. We simply use the `filter` method to check with a particular value.&#xA;&#xA;We can use the shell, to perform some queries. You can use this in your views or viewsets as per your requirements and constraints.&#xA;&#xA;We can even use `explain` to see what the underlying `sql` got executed out from the ORM. The [explain](https://docs.djangoproject.com/en/3.1/ref/models/querysets/#explain) function is similar to the `EXPLAIN ANALYSE` command in the `sql` queries. It gives a bit of context on how the query was executed.&#xA;&#xA;```&#xA;$ python manage.py shell&#xA;```&#xA;&#xA;```python&#xA;&gt;&gt;&gt; from blog.models import Article                                                                &#xA;&gt;&gt;&gt; Article.objects.filter(description=&#39;test 1&#39;)                                                   &#xA;&#xA;&lt;QuerySet [&lt;Article: test&gt;]&gt;                                                                       &#xA;&#xA;&#xA;&gt;&gt;&gt; Article.objects.filter(description=&#39;test 1&#39;).explain()                                         &#xA;&#xA;&#34;Seq Scan on blog_article  (cost=0.00..11.00 rows=1 width=880)\n  Filter: ((description)::text = &#39;t&#xA;est 1&#39;::text)&#34;                                                                                     &#xA;&#xA;&#xA;&gt;&gt;&gt; Article.objects.filter(title=&#39;test&#39;)                                                           &#xA;&#xA;&lt;QuerySet [&lt;Article: test&gt;]&gt;                                                                       &#xA;&#xA;&#xA;&gt;&gt;&gt; Article.objects.filter(title=&#39;test&#39;).explain()                                                 &#xA;&#xA;&#34;Index Scan using blog_article_title_3c514952_like on blog_article  (cost=0.14..8.16 rows=1 width=8&#xA;80)\n  Index Cond: ((title)::text = &#39;test&#39;::text)&#34;                                                 &#xA;&#xA;```&#xA;&#xA;We can use `__contains` for replicating the behaviour of `LIKE` in python/django from SQL. The below example will check if the title has a word `test` in any records of the database.&#xA;&#xA;```&#xA;&gt;&gt;&gt; Article.objects.filter(title__contains=&#39;test&#39;)&#xA;&#xA;&lt;QuerySet [&lt;Article: test&gt;, &lt;Article: testpost&gt;]&gt; &#xA;```&#xA;&#xA;BONUS: We can even get the underlying SQL with the `.query.__str__()` method. &#xA;&#xA;```&#xA;articles = Article.objects.filter(title__contains=&#39;test&#39;)&#xA;&#xA;articles.query.__str__()&#xA;```&#xA;&#xA;```&#xA;&#39;SELECT &#34;blog_article&#34;.&#34;id&#34;, &#34;blog_article&#34;.&#34;title&#34;, &#34;blog_article&#34;.&#34;description&#34;, &#34;blog_article&#34;.&#34;&#xA;content&#34;, &#34;blog_article&#34;.&#34;status&#34; FROM &#34;blog_article&#34; WHERE &#34;blog_article&#34;.&#34;title&#34;::text LIKE %test&#xA;%&#39;&#xA;```&#xA;&#xA;Here, we are able to see that clearly, that the django orm used the `LIKE` clause for comparing the title.&#xA;&#xA;Further readings and references: &#xA;&#xA;- [Indexing in Postgres](https://medium.com/geekculture/indexing-in-postgres-db-4cf502ce1b4e)&#xA;- [Indexing refernece Django docs](https://docs.djangoproject.com/en/4.1/ref/models/indexes/)&#xA;- [Non-Clustered indexing](https://gudevsoc.com/what-is-non-clustered-index-in-sql-with-example/)</content>
      <type>til</type>
    </item>
    <item>
      <title>Django Bulk Update QuerySet objects</title>
      <link>https://www.meetgor.com/til/django-bulk-update-queryset</link>
      <description>Using bulk_update to update multiple objects in one go.</description>
      <pubDate>Mon, 31 Oct 2022 00:00:00 UTC</pubDate>
      <content>Let&#39;s say, I have a lots of objects which I want to update with a particular field or fields. We can use the [bulk_update](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#bulk-update) method with the model name.&#xA;&#xA;```python&#xA;# blog/models.py&#xA;&#xA;from django.db import models&#xA;&#xA;ARTICLE_STATUS = [&#xA;    (&#34;PUBLISHED&#34;, &#34;Published&#34;),&#xA;    (&#34;DRAFT&#34;, &#34;Draft&#34;),&#xA;]&#xA;&#xA;class Article(models.Model):&#xA;    title = models.CharField(max_length=128)&#xA;    description = models.CharField(max_length=512)&#xA;    content = models.TextField()&#xA;    status = models.CharField(max_length=16, choices=ARTICLE_STATUS, default=&#34;DRAFT&#34;)&#xA;&#xA;    def __str__(self):&#xA;        return self.title&#xA;&#xA;```&#xA;&#xA;Let&#39;s say we have a simple model `Article` with a few typical attributes like `title`, `description`, `content`, and `status`. We have the status as a choice field from two options as `Draft` and `Published`. It could be a boolean field, but that looks too gross for a article status.&#xA;&#xA;&#xA;```python&#xA;&#xA;from blog.models import Article&#xA;&#xA;articles = Article.objects.filter(status=&#34;draft&#34;)&#xA;&#xA;for i in range(len(articles)):&#xA;    articles[i].status = &#34;published&#34;&#xA;&#xA;Article.objects.bulk_update(articles, [&#34;status&#34;,])&#xA;&#xA;```&#xA;&#xA;&#xA;In the above code, the `Articles` model is filtered by the status of `draft`. We iterate over the `QuerySet` which will contain the objects of the articles, by setting the object&#39;s properties to the value we want to set. We are jsut setting the value of the property of the object for each object.&#xA;&#xA;This just makes a changes to the `QuerySet`, by using the `bulk_update` method, the two parameters required are the `QuerySet` and the list of `fields` which are to be updated. The function returns the number of objects/records updated.&#xA;&#xA;```python&#xA;&gt;&gt;&gt; from blog.models import Article&#xA;&gt;&gt;&gt; articles = Article.objects.filter(status=&#34;DRAFT&#34;)&#xA;&gt;&gt;&gt; articles&#xA;&lt;QuerySet [&lt;Article: test 1&gt;, &lt;Article: test 3&gt;]&gt;&#xA;&#xA;&gt;&gt;&gt; for i in range(len(articles)):&#xA;...     articles[i].status = &#34;PUBLISHED&#34;&#xA;...&#xA;&gt;&gt;&gt; articles&#xA;&lt;QuerySet [&lt;Article: test 1&gt;, &lt;Article: test 3&gt;]&gt;&#xA;&gt;&gt;&gt;&#xA;&#xA;&gt;&gt;&gt; Article.objects.bulk_update(articles, [&#39;status&#39;,])&#xA;2&#xA;&#xA;&gt;&gt;&gt; Article.obejcts.get(title=&#34;test 1&#34;).status&#xA;&#39;PUBLISHED&#39;&#xA;&#xA;&gt;&gt;&gt; Article.objects.filter(status=&#34;DRAFT&#34;)&#xA;&lt;QuerySet []&gt;&#xA;&gt;&gt;&gt;&#xA;```&#xA;&#xA;As, we can see here there were two obejcts `test 1` and `test 2` objects with the status as `Draft`. By iterating over the queryset and assigning the status of the object to published, the query set was changed and modified locally.&#xA;By using the `bulk_update` method, we parsed the queryset and the list of attributes to be updated into the function. This gives us the number of objects which were updated, in this case `2`. We then look into the article actual record in the database and it has indeed updated to the value we set in this operation.</content>
      <type>til</type>
    </item>
    <item>
      <title>Python Pipreqs: Generate requirements file from the imported packages</title>
      <link>https://www.meetgor.com/til/python-pipreqs</link>
      <description>Exploring the pipreqs package that allows to list all the dependencies or packages which are imported in a python project</description>
      <pubDate>Wed, 14 Sep 2022 00:00:00 UTC</pubDate>
      <content>## Introduction&#xA;&#xA;[Pipreqs](https://pypi.org/project/pipreqs/) is a python package that allows us to list all the pacakges which are imported in a python project. This is a great package for reducing the amount of redundant packages for a project. &#xA;&#xA;## Install pipreqs&#xA;&#xA;You can install pipreqs with one of the many ways with pip, pipx, or any other pacakge management tool. I personally use pipreqs with `pipx` as it remains isolated from the rest of my project dependencies.&#xA;&#xA;### Using simple pip install&#xA;&#xA;We can install with pip by creating a virtual environment or in a existing virtual environment.&#xA;&#xA;```&#xA;pip install virtualenv venv&#xA;source venv/bin/activate&#xA;&#xA;pip install pipreqs&#xA;```&#xA;&#xA;### Using pipx&#xA;&#xA;We can install pipreqs with pipx. [Pipx](https://pypi.org/project/pipx/) is also a python package but used as a tool to install any cli specific tool with the isolated environment.&#xA;&#xA;```&#xA;pipx install pipreqs&#xA;pipx run pipreqs&#xA;```&#xA;&#xA;## Using pipreqs&#xA;&#xA;We need to specify the encoding, which is used for reading the files while capturing the imports from the project.&#xA;&#xA;```&#xA;pipx run pipreqs --encoding=utf-8 .&#xA;```&#xA;&#xA;Additionaly, we can specify the `path` or filename where it will be used to save the imported packages. The `--savepath` option takes in the path to the file where you want to generate the list of the packages to be installed.&#xA;&#xA;```&#xA;pipx run pipreqs --encoding=utf-8 --savepath reqs.txt . &#xA;```&#xA;&#xA;Though this doesn&#39;t guarentee all the requirements for a file, it is really helpful for explicitly used packages in the python project.</content>
      <type>til</type>
    </item>
    <item>
      <title>Django-Mermaid</title>
      <link>https://www.meetgor.com/projects/django-mermaid</link>
      <description>Generate ER Diagram for your Django project in Markdown with Mermaid</description>
      <pubDate>Wed, 03 Aug 2022 00:00:00 UTC</pubDate>
      <content>&#xA;&#xA;Create ER Diagrams (Entity Relationship Diagrams) of your Django projects related to different models of associated applications in Mermaid.&#xA;&#xA;Tech Stack:&#xA;- Python&#xA;- Django&#xA;</content>
      <type>projects</type>
    </item>
    <item>
      <title>Django: Get list of all models and associated fields in a django project</title>
      <link>https://www.meetgor.com/til/django-list-models</link>
      <description>Get the list of all the models and associated fields/attributes in a django project or an application</description>
      <pubDate>Tue, 02 Aug 2022 00:00:00 UTC</pubDate>
      <content>## Context&#xA;&#xA;Let&#39;s say we want the list of all the models and associated attributes in all the applications of a django project, we can do that using the [django.apps](https://docs.djangoproject.com/en/4.0/ref/applications/) with apps method. &#xA;&#xA;## Get all the models in a project&#xA;&#xA;To fetch all the models, we can use the [get_models](https://docs.djangoproject.com/en/4.0/ref/applications/#django.apps.AppConfig.get_models) methods, it will return a list of model classes in all the entire project(all applications). We can import all the models in the django project with the command:&#xA; &#xA;```python&#xA;from django.apps import apps&#xA;models = apps.get_models()&#xA;```&#xA;&#xA;```&#xA;[&lt;class &#39;django.contrib.admin.models.LogEntry&#39;&gt;, &lt;class &#39;django.contrib.auth.models.Permission&#39;&gt;, &#xA;&lt;class &#39;django.contrib.auth.models.Group&#39;&gt;, &lt;class &#39;django.contrib.contenttypes.models.ContentType&#39;&gt;,&#xA; &lt;class &#39;django.contrib.sessions.models.Session&#39;&gt;, &lt;class &#39;allauth.account.models.EmailAddress&#39;&gt;, &#xA;&lt;class &#39;allauth.account.models.EmailConfirmation&#39;&gt;, &lt;class &#39;allauth.socialaccount.models.SocialApp&#39;&gt;, &#xA;&lt;class &#39;allauth.socialaccount.models.SocialAccount&#39;&gt;, &lt;class &#39;allauth.socialaccount.models.SocialToken&#39;&gt;, &#xA;&lt;class &#39;user.models.TimeStampedModel&#39;&gt;, &lt;class &#39;user.models.User&#39;&gt;, &lt;class &#39;articles.models.Tags&#39;&gt;,&#xA; &lt;class &#39;articles.models.Series&#39;&gt;, &lt;class &#39;articles.models.Article&#39;&gt;, &lt;class &#39;blog.models.Blog&#39;&gt;]&#xA;```&#xA;&#xA;We are importing the apps and creating a list of the models in our django project. The Django app command will load all the applications in the project, and the [get_models]() method will fetch the associated models. This has resulted in a list of model class objects, we can iterate over them and fetch the required details, we want.&#xA;&#xA;For instance, If I am interested in the name of these models, I can use the `__name__` property to fetch the model&#39;s name. &#xA;&#xA;```python&#xA;from django.apps import apps&#xA;model_list = apps.get_models()&#xA;&#xA;for model in model_list:&#xA;    print(model.__name__)&#xA;```&#xA;&#xA;```&#xA;LogEntry&#xA;Permission&#xA;Group&#xA;ContentType&#xA;Session&#xA;EmailAddress&#xA;EmailConfirmation&#xA;SocialApp&#xA;SocialAccount&#xA;SocialToken&#xA;TimeStampedModel&#xA;User&#xA;Tags&#xA;Series&#xA;Article&#xA;Blog&#xA;&#xA;```&#xA;&#xA;So, from the above example, we can see we have accessed all the model names in our entire django project. &#xA;&#xA;## Access Application name associated with a model&#xA;&#xA;For accessing the name of the application from the model class, we can use the `_meta` attribute followed by the `app_label` property to get the `app_name` associated with the model.&#xA;&#xA;```python&#xA;from django.apps import apps&#xA;model_list = apps.get_models()&#xA;&#xA;for model in model_list:&#xA;    print(f&#34;{model._meta.app_label}  -&gt; {model.__name__}&#34;)&#xA;```&#xA;&#xA;```&#xA;admin  -&gt; LogEntry&#xA;auth  -&gt; Permission&#xA;auth  -&gt; Group&#xA;contenttypes  -&gt; ContentType&#xA;sessions  -&gt; Session&#xA;account  -&gt; EmailAddress&#xA;account  -&gt; EmailConfirmation&#xA;socialaccount  -&gt; SocialApp&#xA;socialaccount  -&gt; SocialAccount&#xA;socialaccount  -&gt; SocialToken&#xA;user  -&gt; TimeStampedModel&#xA;user  -&gt; User&#xA;articles  -&gt; Tags&#xA;articles  -&gt; Series&#xA;articles  -&gt; Article&#xA;blog  -&gt; Blog&#xA;```&#xA;&#xA;In the above example, we can see we have printed all the models with their associated application names. &#xA;&#xA;## Accessing all the attributes associated with a model&#xA; &#xA;To access all the fields/property/attributes associated with a model, we can again use the `_meta` attribute followed by the `get_fields` method.  This method will return a list of field objects. For accessing the name of those attributes/fields, we have to iterate over the list and then further use `name` property.&#xA;&#xA;```python&#xA;from django.apps import apps&#xA;model_list = apps.get_models()&#xA;for model in model_list:&#xA;    print(model.__name__)&#xA;    field_list = model._meta.get_fields()&#xA;    for field in field_list:&#xA;        print(field.name)&#xA;```&#xA;&#xA;```&#xA;LogEntry&#xA;id&#xA;action_time&#xA;user&#xA;content_type&#xA;object_id&#xA;object_repr&#xA;action_flag&#xA;change_message&#xA;Permission&#xA;group&#xA;user&#xA;id&#xA;...&#xA;...&#xA;Blog&#xA;article&#xA;id&#xA;name&#xA;description&#xA;authors&#xA;```&#xA;So, that is how we get all the associated field names in the associated models in our django projects. Also, there are a lot of attributes, we can access with the apps property. The `__dict__.keys()` can be used to get the list of all associated properties or other methods in a class instance.&#xA;&#xA;```&#xA;&gt;&gt;&gt; m[14]._meta.get_fields()[4].__dict__.keys()&#xA;&#xA;dict_keys([&#39;name&#39;, &#39;verbose_name&#39;, &#39;_verbose_name&#39;, &#39;primary_key&#39;, &#39;max_length&#39;, &#39;_unique&#39;, &#39;blank&#39;, &#39;null&#39;, &#39;remote_field&#39;,&#xA; &#39;is_relation&#39;, &#39;default&#39;, &#39;editable&#39;, &#39;serialize&#39;, &#39;unique_for_date&#39;, &#39;unique_for_month&#39;, &#39;unique_for_year&#39;, &#39;choices&#39;, &#xA;&#39;help_text&#39;, &#39;db_index&#39;, &#39;db_column&#39;, &#39;_db_tablespace&#39;, &#39;auto_created&#39;, &#39;creation_counter&#39;, &#39;_validators&#39;, &#39;_error_messages&#39;, &#xA;&#39;error_messages&#39;, &#39;db_collation&#39;, &#39;validators&#39;, &#39;attname&#39;, &#39;column&#39;, &#39;concrete&#39;, &#39;model&#39;])&#xA;```&#xA;In the above example, I am using a list of models and getting the list of all the attributes associated with a field of a model. This can be applied and other properties can be accessed. &#xA;&#xA;## Get Models with a specific app&#xA;&#xA;Let&#39;s say we want all the models associated with a particular application in the project, we can do that by specifying the name of the application.&#xA;&#xA;```python&#xA;from django.apps import apps&#xA;&#xA;app_info = apps.get_app_config(&#39;articles&#39;)&#xA;&#xA;print(app_info.__dict__.keys()&#xA;&#xA;print(app_info.verbose_name)&#xA;&#xA;print(app_info.models)&#xA;&#xA;print(app_info.models[&#39;article&#39;].__dict__.keys())&#xA;```&#xA;&#xA;```&#xA;dict_keys([&#39;name&#39;, &#39;module&#39;, &#39;apps&#39;, &#39;label&#39;, &#39;verbose_name&#39;, &#39;path&#39;, &#39;models_module&#39;, &#39;models&#39;])&#xA;&#xA;&#39;Article&#39;&#xA;&#xA;{&#39;tags&#39;: &lt;class &#39;articles.models.Tags&#39;&gt;, &#39;series&#39;: &lt;class &#39;articles.models.Series&#39;&gt;, &#39;article&#39;: &lt;class &#39;articles.models.Article&#39;&gt;}&#xA;&#xA;dict_keys([&#39;__module__&#39;, &#39;Article_Status&#39;, &#39;__str__&#39;, &#39;get_absolute_url&#39;, &#39;__doc__&#39;, &#39;_meta&#39;, &#39;DoesNotExist&#39;, &#39;MultipleObjectsReturned&#39;, &#39;title&#39;, &#39;description&#39;, &#39;content&#39;, &#39;status&#39;, &#39;get_status_display&#39;, &#39;blog_id&#39;, &#39;blog&#39;, &#39;author_id&#39;, &#39;author&#39;, &#39;timestampedmodel_ptr_id&#39;, &#39;timestampedmodel_ptr&#39;])&#xA;```&#xA;&#xA;So, we can see that we have got the information about the app `articles` in the proejct where we can get the `verbose_name` property to fetch the human-readable format of the article model. Further, we can get all the models associated with the `articles` application. We get back a dict with the model name as the key and the class reference as the value.&#xA;&#xA;We have accessed the `article` model in the `articles` application and fetched all the associated properties or methods in the model.&#xA;&#xA;For further references, you can visit the [django apps documentation](https://docs.djangoproject.com/en/4.0/ref/applications/) to get more relevant methods and properties.</content>
      <type>til</type>
    </item>
    <item>
      <title>Django Blog DevLog: Load Frontmatter data into Template/Model Form Fields</title>
      <link>https://www.meetgor.com/til/django-form-load-frontmatter</link>
      <description>Rendering frontatter from content field into the Template Form field using HTMX and frontmatter libraries</description>
      <pubDate>Mon, 01 Aug 2022 00:00:00 UTC</pubDate>
      <content>## Introduction&#xA;&#xA;I have an Article Form where I load my post into it directly, it might have frontmatter. So what I wish to achieve is when I paste in the content, the frontmatter should be picked up and it should render the form fields like `title`, `description`, and then also remove the frontmatter from the content.&#xA;&#xA;To do that, we will require a model to work with and a form based on that model. We will exclude a few fields from that model so as to process these attributes on the server side. I am working on my Blog project which is a simple Django application.  You can get the source code for the project on the [GitHub repository](https://github.com/mr-destructive/techstructive-blog/).&#xA;&#xA;## Django Project Context&#xA;&#xA;The techstructive-blog is a django project, which has a couple of applications currently, not in a good situation. There are apps like `article`, `blog`, and `user`. This project has templates and static folder in the base directory. The project is deployed on [railway](https://www.railway.app) this is an always under development project, you can check out the [techstructive-blog](https://django-blog.up.railway.app). We can get the bits and pieces of the project details required for understanding what I want to do with the following sections.&#xA;&#xA;### Article Model&#xA;&#xA;We have an `Article` model with attributes like `title`, `description`,  `author` as a Foreign Key to the user model, and a few other attributes which is not related to what we are trying to achieve right now or at least don&#39;t require an explanation. We have a model method called `get_absolute_url` for getting a URL in order to redirect the client when the model instance is created or updated from the backend. You can definitely check out the details of these small components or templates in the project repository. &#xA;&#xA;```python&#xA;# articles/models.py&#xA;&#xA;&#xA;class Article(TimeStampedModel):&#xA;    class Article_Status(models.TextChoices):&#xA;        DRAFT = (&#xA;            &#34;DRAFT&#34;,&#xA;            _(&#34;Draft&#34;),&#xA;        )&#xA;        PUBLISHED = (&#xA;            &#34;PUBLISHED&#34;,&#xA;            _(&#34;Published&#34;),&#xA;        )&#xA;&#xA;    title = models.CharField(max_length=128)&#xA;    description = models.CharField(max_length=256)&#xA;    content = models.TextField(default=&#34;&#34;, null=False, blank=False)&#xA;    status = models.CharField(&#xA;        max_length=16,&#xA;        choices=Article_Status.choices,&#xA;        default=Article_Status.DRAFT,&#xA;    )&#xA;    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, null=True, blank=True)&#xA;    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name=&#34;author&#34;)&#xA;&#xA;    def __str__(self):&#xA;        return self.title&#xA;&#xA;    def get_absolute_url(self):      &#xA;        return reverse(&#39;articles:article-detail&#39;, args=[str(self.id)])&#xA;```&#xA;&#xA;In the below snippet, we have the forms defined in the article application for creating or updating of article instance.  We will be using model forms as our form data should contain fields related to a model in this case the `Article` model. So when we inherit the `forms.ModelForm` in our custom `ArticleForm` we simply need to specify the model and it will take in all the attributes of that model by default, but if we specify the `fields` or `exclude` tuples, it will include only or exclude only the provided list of attributes from the model. &#xA;&#xA;We have also added the widgets for the form fields which will allow us to customize the way the fields in the template/form will render. We can specify the HTML attributes like `width`, `height`, `style`, etc.  &#xA;&#xA;### Article Form&#xA;&#xA;```python&#xA;# article/forms.py&#xA;&#xA;&#xA;from django import forms&#xA;from .models import Article&#xA;&#xA;&#xA;class ArticleForm(forms.ModelForm):&#xA;    class Meta:&#xA;        model = Article&#xA;        exclude = (&#xA;            &#34;created&#34;,&#xA;            &#34;updated&#34;,&#xA;            &#34;author&#34;,&#xA;        )&#xA;        widgets = {&#xA;            &#34;title&#34;: forms.TextInput(&#xA;                attrs={&#xA;                    &#34;class&#34;: &#34;form-control&#34;,&#xA;                    &#34;style&#34;: &#34;max-width: 450px; align: center;&#34;,&#xA;                    &#34;placeholder&#34;: &#34;Title&#34;,&#xA;                }&#xA;            ),&#xA;            &#34;description&#34;: forms.TextInput(&#xA;                attrs={&#xA;                    &#34;class&#34;: &#34;form-control&#34;,&#xA;                    &#34;style&#34;: &#34;max-width: 900px; &#34;,&#xA;                    &#34;placeholder&#34;: &#34;Description&#34;,&#xA;                }&#xA;            ),&#xA;            &#34;content&#34;: forms.Textarea(&#xA;                attrs={&#xA;                    &#34;class&#34;: &#34;form-control post-body&#34;,&#xA;                    &#34;id&#34;: &#34;text-content&#34;,&#xA;                    &#34;style&#34;: &#34;max-width:900px;&#34;,&#xA;                    &#34;hx-post&#34;: &#34;/article/meta/&#34;,&#xA;                    &#34;placeholder&#34;: &#34;Content&#34;,&#xA;                }&#xA;            ),&#xA;            &#34;blog&#34;: forms.Select(&#xA;                attrs={&#xA;                    &#34;class&#34;: &#34;form-control&#34;,&#xA;                    &#34;placeholder&#34;: &#34;Blog Publication&#34;,&#xA;                }&#xA;            ),&#xA;        }&#xA;&#xA;```&#xA;&#xA;So, these are my models and form files in the article app. Using htmx and without any javascript I want to update the form so that it picks up the front matter in the content field which is a text area and fills the title, description other attributes automatically for me. &#xA;&#xA;This can be done in a lot of ways, but I will be sharing one of the ways that I recently used in my blog project. This process involves creating a class-based view and adding a `POST` method that won&#39;t post any data to the backend but will send necessary data to the view.&#xA;&#xA;&#xA;Let&#39;s see the process before diving into any of the code:&#xA;&#xA;## Gist of the Process&#xA;&#xA;- Attach a `hx-post` attribute to the form field for sending the request to a view&#xA;- When the request is sent, the data is loaded with `request.POST`, it is cleaned and converted in python-readable format with json.&#xA;- Once we have the data, we try to use the `frontmatter.loads` function that will load the content and if we have a frontmatter in the text, it will load it as a `frontmatter.POST` object.&#xA;- We will extract `title`, `description`, and other data fields from the object.&#xA;- We will initialize a Form instance of Article, with the initial data values as the extracted data from the frontmatter.&#xA;- Now we have two options:&#xA;    - If the article instance already exists i.e. we are updating the article&#xA;   - Else we are creating a new article&#xA;&#xA;Accordingly, we will load the form in the respective templates i.e. `update.html` for updating the existing articles and `article-form.html` for a new article.&#xA;&#xA;## Adding HTMX Magic&#xA;&#xA;If you&#39;d have seen we have a `hx-post` attribute in the `article/forms.py` file, the `content` widget has a `hx-post` attribute which sends a post request to the `article/meta/` URL route. This route we will bind to the `ArticleMetaView` which we will define in a few moments. This is usually sent once we change a certain text in the content field, so we can modify it as per your requirement with `hx-trigger` that can enable us to specify the trigger event or the type of trigger we want. Further, you can read from the [htmx docs](https://htmx.org/docs/#trigger-modifiers) about these triggers and other attributes. &#xA;&#xA;```python&#xA;# article/urls.py&#xA;&#xA;from django.urls import path&#xA;from . import views&#xA;&#xA;app_name = &#34;articles&#34;&#xA;&#xA;urlpatterns = [&#xA;    path(&#34;&#34;, views.ArticleCreateView.as_view(), name=&#34;article-create&#34;),&#xA;    path(&#34;&lt;int:pk&gt;/&#34;, views.ArticleDetailView.as_view(), name=&#34;article-detail&#34;),&#xA;    path(&#34;delete/&lt;int:pk&gt;/&#34;, views.ArticleDetailView.as_view(), name=&#34;article-delete&#34;),&#xA;    path(&#34;edit/&lt;int:pk&gt;&#34;, views.ArticleDetailView.as_view(), name=&#34;article-update&#34;),&#xA;&#xA;    # the new view that we will create&#xA;    path(&#34;meta/&#34;, views.ArticleMetaView.as_view(), name=&#34;article-meta&#34;),&#xA;]&#xA;```&#xA;&#xA;## Capture Frontmatter Meta-data View &#xA;&#xA;Along with the Create, Detail/List, Update, Delete View, I will create a separate class called `ArticleMetaView` that will fetch the form fields and render the templates again but this time it will fill in the frontmatter meta-data in the fields if the content is parsed with the relvant frontmatter.&#xA;&#xA;```python&#xA;# articles/view.py&#xA;&#xA;class ArticleMetaView(View):&#xA;    model = Article&#xA;&#xA;    def post(self, request, *args, **kwargs):&#xA;        &#xA;        data = json.loads(json.dumps(dict(request.POST)))&#xA;        loaded_frontmatter = frontmatter.loads(data[&#39;content&#39;][0])&#xA;&#xA;       # frontmatter has keys i.e. attributes like title, description, etc.&#xA;        if dict(loaded_frontmatter):&#xA;            article_title = loaded_frontmatter[&#39;title&#39;]&#xA;            article_description = loaded_frontmatter[&#39;description&#39;]&#xA;            form = ArticleForm(initial={&#39;title&#39;: article_title, &#xA;            &#39;description&#39;: article_description, &#39;content&#39;: loaded_frontmatter.content})&#xA;            context = {&#39;form&#39;: form}&#xA;            article_list = Article.objects.filter(title=article_title)&#xA;            if article_list:&#xA;                article = article_list.last()&#xA;                context[&#39;article&#39;] = article&#xA;                return render(request, &#39;articles/edit_article.html&#39;, context)&#xA;            return render(request, &#39;articles/article_form.html&#39;, context)&#xA;&#xA;        article_list = Article.objects.filter(title=data[&#39;title&#39;][0])&#xA;       &#xA;       # if the article title has been already taken i.e. we are updating an article&#xA;&#xA;        if article_list:&#xA;            article = article_list.last()&#xA;            form = ArticleForm(data=request.POST)&#xA;            context = {&#39;form&#39;: form}&#xA;            context[&#39;article&#39;] = article&#xA;            return render(request, &#39;articles/edit_article.html&#39;, context)&#xA;&#xA;        form = ArticleForm(data=request.POST)&#xA;        context = {&#39;form&#39;: form}&#xA;        return render(request, &#39;articles/article_form.html&#39;, context)&#xA;&#xA;```&#xA;&#xA;In the above `ArticleMetaView` we have created a `post` method as we want to get hold of the content from the form. So, we start by extracting and converting the `request.body` data into an appropriate type for easily working with python. So, the `request.body` will contain the data like `csrf_token`, `form_data`, etc. received from the frontend template. We store the received data as `data` and now from this data, we can load the content field which will have the content information.&#xA;&#xA;Firstly we will extract the `request.body` which will contain the data from the form as we have made a `POST` request to this endpoint. For doing that we need to parse the content in a apropriate format such that it is python friendly. So we wrap the `request.body` into json format and then decode it back into the json string. This will give us the dict of the request data.&#xA;&#xA;```python&#xA;data = json.loads(json.dumps(dict(request.POST)))&#xA;```&#xA;&#xA;```&#xA;{&#39;csrfmiddlewaretoken&#39;: [&#39;bSYJxD39XH509tD1tZGd0WU21PUaKaLeqjjGbyzRvLXF4P8iIxb5l0fmTWVFjELQ&#39;], &#39;title&#39;: [&#39;test2&#39;], &#39;description&#39;: [&#39;test&#39;], &#39;content&#39;: [&#39;test something&#39;], &#39;status&#39;: [&#39;DRAFT&#39;], &#39;blog&#39;: [&#39;&#39;]}&#xA;```&#xA;&#xA;So, this will grab the request data as a dict, we can then extract the data from this as it has data from the Form fields. We are interested in the content field in the Form, so we can get it by specifying the key `content` from the extracted data. But as we can see the data doesn&#39;t contain the actual data instead it is wrapped in a list i.e. `[&#39;test something&#39;]`, so we will have to index it and then fetch it.&#xA;&#xA;```python&#xA;content_string = data[&#39;content&#39;][0]&#xA;```&#xA;&#xA;This will give us the exact content field as a string. So, we can now move into extracting the frontmatter from the fields. &#xA;&#xA;Now, we can use the [frontmatter](https://python-frontmatter.readthedocs.io/en/latest/index.html) library to parse the content into the [loads](https://python-frontmatter.readthedocs.io/en/latest/api.html#frontmatter.loads) funciton and extract the frontmatter if it is present in the content field. The frontmatter library has a `loads` function which takes in a string and can give out a [frontmatter.Post](https://python-frontmatter.readthedocs.io/en/latest/api.html#post-objects) object. The loads function is differnet from the [load](https://python-frontmatter.readthedocs.io/en/latest/api.html#frontmatter.load) function as the load frunciton is for reading data from a stream of bytes i.e. a file or othe related byte object. The differnece is subtle but it took a read at the [documentation](https://python-frontmatter.readthedocs.io/en/latest/api.html#module-frontmatter).&#xA;&#xA;```python&#xA;post = data[&#39;content&#39;][0]&#xA;loaded_frontmatter = frontmatter.loads(post)&#xA;```&#xA;&#xA;This wil load the content and give us a `frontmatter.Post` as said earlier. This will contain a dict with all the frontmatter if it has any and will by default parse the non-frontmatter data i.e. the remaining text into the `content` key. We need a chack if the Form field had any fronmatter this can be checked by the `dict(loaded_frontmatter)` which will return None if it cannot load the frontmatter.&#xA;&#xA;```python&#xA;loaded_frontmatter = frontmatter.loads(data[&#39;content&#39;][0])&#xA;if dict(loaded_frontmatter):&#xA;  print(loaded_frontmatter.keys())&#xA;```&#xA;&#xA;```&#xA;dict_keys([&#39;templateKey&#39;, &#39;title&#39;, &#39;description&#39;, &#39;date&#39;, &#39;status&#39;])&#xA;```&#xA;&#xA;So once we have the frontmatter loaded we can get specific keys from it and initialize the form vaues to them. But we have made clear distictions that we want to perform a specific task if we have frontmatter keys in the content field of the Form else we can do something else.&#xA;&#xA;First let&#39;s handle the loading of the frontmatter into the form. For doing that we will get all the required attributes from the frontmatter like `title`, `description`, `content`, etc which can be accessed normally as we extract the value from a key in a dict.&#xA;&#xA;Once we have got those keys, we can start filling in the Form data with initial values. The [Django Model form](https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/) takes in a parameter like [initial](https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#providing-initial-values) which can be a dict of the fiields along with the value that can be used for rendering the form initially when we load the template.&#xA;&#xA;```python&#xA;article_title = loaded_frontmatter[&#39;title&#39;]&#xA;article_description = loaded_frontmatter[&#39;description&#39;]&#xA;&#xA;form = ArticleForm(initial={&#39;title&#39;: article_title, &#39;description&#39;: article_description, &#39;content&#39;: loaded_frontmatter.content})&#xA;```&#xA;&#xA;This will take in a `ArticleForm` and fill the initial values like `title`, `description`, etc which we have provided in the dict with the values. Now, we need to parse this form in the current template or re-render the template. But before that, we need to parse this context into the template. We will create a dict with `form` as the key which can be used to render in the template.&#xA;&#xA;Also, we have a two ways here, either the user is creating a new article or it is updating a existing article. We need to make sure that we preserve the initial fields in the form as we are updating the existing article. So, we can filter the article objects as per the title of the current title and then if we find a article with that title, we will parse the context with that article object.&#xA;&#xA;```python&#xA;article_list = Article.objects.filter(title=article_title)&#xA;if article_list:&#xA;    article = article_list.last()&#xA;    context{&#xA;      &#39;form&#39;: form,&#xA;      &#39;article&#39;: article&#xA;    }&#xA;    return render(request, &#39;articles/edit_article.html&#39;, context)&#xA;context = {&#39;form&#39;: form}&#xA;return render(request, &#39;articles/article_form.html&#39;, context)&#xA;```&#xA;&#xA;Now, we have form data along with the article instance used for rendering the form with appropriate content. So, this will work for editing an already existing article. For a new article, we have to simply parse the form to the template and it will render the title picked from the fotnmatter or leave it empty.&#xA;&#xA;Similarly, for the article with no frontmatter we will iterate over the article and if the article&#39;s title already exist, we will render the article data with the form else render the form with the parsed title and other meta-data in the form.&#xA;&#xA;&#xA;&lt;video width=&#34;800&#34; height=&#34;450&#34; controls&gt;&#xA;  &lt;source src=&#34;https://res.cloudinary.com/techstructive-blog/video/upload/v1659370006/blog-media/frontmatter-load-htmx.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;&lt;/video&gt;&#xA;&#xA;So that is how we render the form data with frontmatter into appropriate meta-data in the form. We have used Django forms and make use of HTMX for the dynamic updation of form.</content>
      <type>til</type>
    </item>
    <item>
      <title>APTUI</title>
      <link>https://www.meetgor.com/projects/aptui</link>
      <description>A terminal interface for testing and working with APIs and web requests.</description>
      <pubDate>Wed, 22 Jun 2022 00:00:00 UTC</pubDate>
      <content>&#xA;&#xA;A terminal client interface for testing and working with APIs and web requests.&#xA;&#xA;Tech Stack:&#xA;&#xA;- Python&#xA;    - Textual&#xA;    - Rich&#xA;&#xA;</content>
      <type>projects</type>
    </item>
    <item>
      <title>Autoformat Python file with Black after saving in Vim</title>
      <link>https://www.meetgor.com/til/vim-python-black-autoformat</link>
      <description>Automatically format python code in the current file after saving the file in Vim.</description>
      <pubDate>Tue, 29 Mar 2022 00:00:00 UTC</pubDate>
      <content>If you are like me who writes Python very badly, it has empty lines with whitespaces, no proper format in assigning variables, not formatted according to [PEP 8](https://peps.python.org/pep-0008/) standards, and you use Vim as your text editor then my friend you need a autocmd badly for it.&#xA;&#xA;## Install Black in Python&#xA;&#xA;Install the [black](https://pypi.org/project/black/) package in python globally or locally as per your preferences.&#xA;&#xA;```&#xA;pip install black&#xA;```&#xA;&#xA;OR with pipx&#xA;&#xA;```&#xA;pipx install black&#xA;```&#xA;&#xA;For a detailed guide about running packages with pipx head toward my article on [pipx](https://mr-destructive.github.io/techstructive-blog/pipx-intro/).&#xA;&#xA;## Set up Autocmd in Vim&#xA;&#xA;We can set up a autocmd. What is a autocmd? It is about running commands when certain events occur like running a command when a file is saved, a buffer is opened or closed, and so on. What we want is to run the black command from the shell when the current file is saved. &#xA;&#xA;So, we can create a autocmd as follows:&#xA;&#xA;```vimscript&#xA;autocmd BufWritePost * !black %&#xA;```&#xA;&#xA;Now, this will run when any type of file is saved, so we will make it specific to python by adding a `*.py` to add in the autocmd.&#xA;&#xA;```vimscript&#xA;autocmd BufWritePost *.py !black %&#xA;```&#xA;&#xA;This works, but it gives a prompt after the command has been executed, to run the command silently we can simply add the silent keyword before the execution of the command from the shell.&#xA;&#xA;```vimscript&#xA;autocmd BufWritePost *.py silent !black %&#xA;```&#xA;&#xA;This looks perfect! &#xA;&#xA;But still, we need to add a auto-group(`augroup`) that groups the autocmds and by adding `autocmd!` it will clear all the commands from the group. &#xA;&#xA;```vimscript&#xA;augroup python_format&#xA;    autocmd!&#xA;    autocmd BufWritePost *.py silent !black %&#xA;augroup end&#xA;```&#xA;We can now add it to the vimrc to work all the time.&#xA;&#xA;## Using pipx &#xA;&#xA;If you have used pipx to install black, you need to setup the autocmd a bit differently. &#xA;&#xA;```vimscript&#xA;autocmd BufWritePost *.py silent !pipx run black %&#xA;```&#xA;&#xA;It might be a bit slower than running with global installation, but it is a neat way to run python package. &#xA;&#xA;So, that&#39;s it we can now write clean and safe python code without breaking a sweat in Vim. Happy Coding :)</content>
      <type>til</type>
    </item>
    <item>
      <title>Feedparser: Python package for reading RSS feeds</title>
      <link>https://www.meetgor.com/posts/python-feedparser</link>
      <description>Introduction is a simple but powerful python package that can be used to extract information about a specific webpage or a publication with its RSS feed(not onl</description>
      <pubDate>Sat, 26 Mar 2022 00:00:00 UTC</pubDate>
      <content>## Introduction&#xA;&#xA;[Feedparser](https://pypi.org/project/feedparser/) is a simple but powerful python package that can be used to extract information about a specific webpage or a publication with its RSS feed(not only RSS). By providing the RSS feed link, we can get structured information in the form of python lists and dictionaries. It can be basically used in a pythonic way to read RSS feeds, it is really simple to use and it even normalizes different types of feeds.&#xA;&#xA;Today, we will be taking a look at the feedparser package in python and how to extract information from a given RSS feed.&#xA;&#xA;## What is feedparser&#xA;&#xA;Feedparser is a python package for parsing feeds of almost any type such as RSS, Atom, RDF, etc. It is a package that allows us to parse or extract information using python semantics. For example, all the latest posts from a given blog can be accessed on a list in python, further different attributes like links, images, titles, descriptions, can be accessed within a dictionary as key-value pairs. &#xA;&#xA;## Installing feedparser&#xA;&#xA;As feedparser is a python package you can install it with pip very easily.&#xA;&#xA;```&#xA;pip install feedparser&#xA;```&#xA;&#xA;This will install feedparser in your respective python environment, it can be a virtual environment or a global environment. &#xA;&#xA;&#xA;## Using feedparser&#xA;&#xA;To test out feedparser, you can open up a python repl, in the environment where you installed the Feedparser package.&#xA;&#xA;```&#xA;python&#xA;```&#xA;&#xA;Firstly import the package.&#xA;&#xA;```python&#xA;import feedparser&#xA;```&#xA;&#xA;Now, we can use the module in our application to get all of the functions or methods from the package.&#xA;&#xA;## Parse an RSS feed URL&#xA;&#xA;To parse an RSS feed link, we can simply use the `parse` function from the feedparser package. The [parse](https://feedparser.readthedocs.io/en/latest/introduction.html) function takes in a string that can be a URL or a file path. Generally, the URL seems to be more useful. So, we can look up any RSS feed on the internet like your blog&#39;s feed, publications feeds, and so on. &#xA;&#xA;```python&#xA;feedparser.parse(&#34;url_of_the_rss_feed&#34;)&#xA;```&#xA;&#xA;The parse function basically fetches the feed from the provided URL or the file. It extracts the feed in a systematic way storing each piece of information in a structured format. At the high level, it returns a dictionary with a few key-value pairs. Further, each key might have a list or nested dictionaries in it. The key identifiers are named in a uniform manner for any feed you parse in the function. Though there might be a few cases where there might be additional information to be parsed, it can even add more information ad shape the structure accordingly.&#xA;&#xA;This will give you a dictionary in python, that can have more or less similar keys. The most common keys that can be used in extracting information are `entries` and `feed`. We can get all the keys associated with a feed that is parsed using the `keys` function.&#xA;&#xA;```python&#xA;feedparser.parse(&#34;url_of_the_rss_feed&#34;).keys()&#xA;```&#xA;&#xA;![Feedparser Keys](https://res.cloudinary.com/techstructive-blog/image/upload/v1648370871/blog-media/ph6bsxobyifqmusumirx.png)&#xA;&#xA;The keys function basically gets all the keys in the dictionary in python.&#xA;&#xA;```&#xA;&gt;&gt;&gt; feedparser.parse(&#34;https://dev.to/feed/&#34;).keys()&#xA;dict_keys([&#39;bozo&#39;, &#39;entries&#39;, &#39;feed&#39;, &#39;headers&#39;, &#39;etag&#39;, &#39;href&#39;, &#39;status&#39;, &#39;encoding&#39;, &#39;version&#39;, &#39;namespaces&#39;])&#xA;```   &#xA;&#xA;This will give out a list of all the keys in the feed which we have parsed from the RSS feed previously. From this list of keys, we can extract the required information from the feed.&#xA;&#xA;Before we extract content from the feed, we can store the dictionary that we get from calling the parse function. We can assign it to a variable and store the dictionary for later use.&#xA;&#xA;```python&#xA;feed = feedparser.parse(&#34;url_of_the_rss_feed&#34;)&#xA;```&#xA;&#xA;&#xA;## Extract the contents from the feed&#xA;&#xA;Now, we have the dictionary of the feed, we can easily access the values from the listed keys. We can get the list of all the posts/podcasts/entries or any other form of content the feed is serving for from the `entries` key in the dictionary. &#xA;&#xA;To get more information and the most possible keys in the returned dictionary, you can refer to the feedparser [reference list](https://feedparser.readthedocs.io/en/latest/reference.html)&#xA;&#xA;### Access Articles from Feed&#xA;&#xA;To access the articles from the feed, we can access those as a list of the articles. Using the `entries` key in the dictonary as follows:&#xA;&#xA;```python&#xA;feedparser.parse(&#34;url_of_the_rss_feed&#34;)[&#34;entries&#34;]&#xA;&#xA;OR&#xA;&#xA;feedparser.parse(&#34;url_of_the_rss_feed&#34;).entries&#xA;```&#xA;&#xA;If you have already defined a variable set to the parse function, you can use that for more efficient extraction.&#xA;&#xA;```python&#xA;feed = feedparser.parse(&#34;url_of_the_rss_feed&#34;)&#xA;&#xA;feed[&#39;entries&#39;]&#xA;&#xA;OR &#xA;&#xA;feed.entries&#xA;```&#xA;&#xA;### Get Number of Articles/Entries from Feed&#xA;&#xA;To get the number of entries in the list, we can simply use the len function in python.&#xA;&#xA;```python&#xA;len(feed.entries)&#xA;&#xA;OR &#xA;&#xA;len(feedparser.parse(&#34;url_of_the_rss_feed&#34;).entries)&#xA;```&#xA;&#xA;![Feedparser Number of Entries](https://res.cloudinary.com/techstructive-blog/image/upload/v1648371042/blog-media/didijxcvsgvl4scrnhje.png)&#xA;&#xA;This gives us the number of entries in the provided feed. This is basically the list that stores all the content from the publication/website. So, we can iterate over the list and find all the different attributes we can extract.&#xA;&#xA;### Get details of the entries from the feed&#xA;&#xA;To get detail information about a particular article/entry in the feed, we can iterate over the `feed.entries` list and access what we require. &#xA;&#xA;So, we will iterate over the entries and simply print those entries one by one to inspect what and how we can extract them. &#xA;&#xA;```python&#xA;for entry in feed.entries:&#xA;  print(entry)&#xA;```&#xA;&#xA;It turns out that every entry in the list is a dictionary again containing a few key-value pairs like `title`, `summary`, `link`, etc. To get a clear idea of those keys we can again use the keys function in python.&#xA;&#xA;```python&#xA;feed = feedparser.parse(&#34;url_of_the_rss_feed&#34;)&#xA;print(feed.entries[0].keys())&#xA;```&#xA;&#xA;![Feedparser Entries Keys](https://res.cloudinary.com/techstructive-blog/image/upload/v1648371221/blog-media/c8uog85goe9jzrzl1pq1.png)&#xA;&#xA;```python&#xA;&gt;&gt;&gt; feed.entries[0].keys()&#xA;dict_keys([&#39;title&#39;, &#39;title_detail&#39;, &#39;authors&#39;, &#39;author&#39;, &#39;author_detail&#39;, &#39;published&#39;, &#39;published_parsed&#39;, &#39;links&#39;, &#39;link&#39;, &#39;id&#39;, &#39;guidislink&#39;, &#39;summary&#39;, &#39;summary_detail&#39;, &#39;tags&#39;])&#xA;```&#xA;&#xA;Now, we have all the keys associated with the entries we can now extract the specific details like the content, like `title`, `author`, `summary_detail`(actual content in this case).  &#xA;&#xA;Though this might not be the same for all RSS feeds, it might be very similar and a matter of using the right keyword for the associated keys in the list of dictionaries.  &#xA;&#xA;Let&#39;s say, we want to print out the titles of all the entries in the feed, we can do that by iterating over the entries list and fetching the title from the iterator as `entry.title` if `entry` is the iterator.&#xA;&#xA;```python&#xA;for entry in feed.entries:&#xA;  print(entry.title)&#xA;```&#xA;&#xA;![Feedparser List of Entries](https://res.cloudinary.com/techstructive-blog/image/upload/v1648372532/blog-media/lhofdzmr3ks0fuut7pxm.png)&#xA;&#xA;Similarly, we will get the links of the entries using the link key in the dictionary.&#xA;&#xA;```python&#xA;for entry in feed.entries:&#xA;  print(entry.link)&#xA;```&#xA;&#xA;### Get information about the Website/Publication&#xA;&#xA;To get the metadata about the information you are extracting from i.e. the website or any publication, we can use the key `feed`. This key stores another dictionary as its value which might have information like `title`, `description` or `subtitle`, `canonical_url`, or any other data related to the website company.&#xA;&#xA;```python&#xA;feed.feed&#xA;&#xA;or&#xA;&#xA;feedparser.parse(&#34;url_of_the_rss_feed&#34;).feed&#xA;```&#xA;&#xA;![Feedparser Feed](https://res.cloudinary.com/techstructive-blog/image/upload/v1648373487/blog-media/r7hiojfdrtrjqfhkjbdt.png)&#xA;&#xA;From this dictionary, we can now simply extract the specific information from the keys. But first, as in the previous examples, we need a clear idea of what are the keys in the dictionary where we can extract the specific value.&#xA;&#xA;```python&#xA;feed.feed.keys()&#xA;&#xA;or&#xA;&#xA;feedparser.parse(&#34;url_of_the_rss_feed&#34;).feed.keys()&#xA;```&#xA;&#xA;Using the keys like `title`, `links`, `subtitle`, we can get the information on the website/company level and not related to the specific post in the entries list. &#xA;&#xA;```python&#xA;# get the title of the webpage/publication&#xA;feed.feed.title&#xA;&#xA;# get the links associated with the webpage&#xA;feed.feed.links&#xA;&#xA;# get the cover-image for the webpage&#xA;feed.feed.image&#xA;``` &#xA;&#xA;You can further get information specific to your feed. &#xA;&#xA;## Checking for keys existence in the dictionary of feed&#xA;&#xA;We also need to check for the existence of a key in a dictionary in the provided feed, this can be a good problem if we are parsing multiple RSS feeds which might have a different structure. This problem occurred to me in the making of [podevcast](https://podevcast.netlify.app) where I had to parse multiple RSS feeds from different RSS generators. Some of the feeds had the cover image but most of them didn&#39;t. So, we need to make sure we have a check over those missing keys.&#xA;&#xA;```python&#xA;feedlist = [&#39;https://freecodecamp.libsyn.com/rss&#39;, &#39;https://feeds.devpods.dev/devdiscuss_podcast.xml&#39;]&#xA;&#xA;for feed in feedlist:&#xA;    feed = feedparser.parse(feed)&#xA;&#xA;    print(feed.entries[0].keys())&#xA;    for entry in feed.entries:&#xA;        if &#39;image&#39; in entry:&#xA;            image_url = entry.image&#xA;        else:&#xA;            image_url = feed.feed.image&#xA;        &#xA;        #print(image_url)&#xA;```&#xA;&#xA;```python&#xA;&gt;&gt;&gt; feedlist = [&#39;https://freecodecamp.libsyn.com/rss&#39;, &#39;https://feeds.devpods.dev/devdiscuss_podcast.xml&#39;]&#xA;&gt;&gt;&gt; for feed in feedlist:&#xA;...     feed = feedparser.parse(feed)&#xA;...     for entry in feed.entries:&#xA;...             if &#39;image&#39; in entry:&#xA;...                     image_url = entry.image&#xA;...             else:&#xA;...                     image_url = feed.feed.image&#xA;...     print(feed.entries[0].keys())&#xA;...&#xA;&#xA;dict_keys([&#39;title&#39;, &#39;title_detail&#39;, &#39;itunes_title&#39;, &#39;published&#39;, &#39;published_parsed&#39;, &#39;id&#39;, &#39;guidislink&#39;, &#39;links&#39;, &#39;link&#39;, &#39;image&#39;, &#39;summary&#39;, &#39;summary_detail&#39;, &#39;content&#39;, &#39;itunes_duration&#39;, &#39;itunes_explicit&#39;, &#39;subtitle&#39;, &#39;subtitle_detail&#39;, &#39;itunes_episode&#39;, &#39;itunes_episodetype&#39;, &#39;authors&#39;, &#39;author&#39;, &#39;author_detail&#39;])&#xA;&#xA;dict_keys([&#39;title&#39;, &#39;title_detail&#39;, &#39;links&#39;, &#39;link&#39;, &#39;published&#39;, &#39;published_parsed&#39;, &#39;id&#39;, &#39;guidislink&#39;, &#39;tags&#39;, &#39;summary&#39;, &#39;summary_detail&#39;, &#39;content&#39;, &#39;subtitle&#39;, &#39;subtitle_detail&#39;, &#39;authors&#39;, &#39;author&#39;, &#39;author_detail&#39;, &#39;itunes_explicit&#39;, &#39;itunes_duration&#39;])&#xA;```&#xA;&#xA;As we can see we do not have an image key in the second RSS feed which means each entry doesn&#39;t have a unique cover image, so we have to fetch the image from the `feed` key then the `image` key in the entries list.&#xA;&#xA;![Feedparser Cover Image Demo](https://res.cloudinary.com/techstructive-blog/image/upload/v1648373275/blog-media/fzdqie5dubigxzfhtv2x.png)&#xA;&#xA;&#xA;As we can see here, the image_url will pick up the `image` key in the dictionary if it is present else we will assign it to another URL which is the website/podcast cover image. This is how we handle exceptions in providing the keys when there are multiple feeds to be extracted though they are quite similar, they will have subtle changes like this that need to be handled and taken care of.&#xA;&#xA;&#xA;## Conclusion&#xA;&#xA;From this little article, we were able to understand and use the feedparser Python package which can be used to extract information from different feeds. We saw how to extract contents for the entries, a number of entries in the feed, check for keys in the dictionary, and so on. Using Python&#39;s Feedparser package, some of the projects I have created include:&#xA;&#xA;- [podevcast](https://podevcast.netlify.app)&#xA;- [dailydotdev-bookmark-cli](https://pypi.org/project/dailydotdev-bookmark-cli/)&#xA;- [Django Newsletter](https://github.com/Mr-Destructive/newsletter)&#xA;&#xA;For further reading, you can specifically target a feed type by getting the appropriate methods from the feedparser [documentation](https://feedparser.readthedocs.io/en/latest/)&#xA;&#xA;Thank you for reading, if you have any suggestions, additions, feedback, please let me know in the comments or my social handles below. Hope you enjoyed reading. Happy Coding :)&#xA;</content>
      <type>posts</type>
    </item>
    <item>
      <title>Podevcast</title>
      <link>https://www.meetgor.com/projects/podevcast</link>
      <description>Developer podcast at a single place</description>
      <pubDate>Fri, 25 Feb 2022 00:00:00 UTC</pubDate>
      <content>&#xA;&#xA;Tech Stack:&#xA;- Python&#xA;- Python Pacakges&#xA;    - feedparser&#xA;- Netlify&#xA;- GitHub Actions&#xA;&#xA;</content>
      <type>projects</type>
    </item>
    <item>
      <title>Crossposter</title>
      <link>https://www.meetgor.com/projects/crossposter</link>
      <description>crosspost your articles to devto, codenewbie, medium and hashnode</description>
      <pubDate>Fri, 25 Feb 2022 00:00:00 UTC</pubDate>
      <content>&#xA;&#xA;Tech Stack:&#xA;- Python&#xA;- Python Pacakges&#xA;&#xA;</content>
      <type>projects</type>
    </item>
  </channel>
</rss>