You can use uv to help turn a Python1 script into a self-contained executable. Useful for really important stuff, like… discovering the next public holiday from your terminal?
All the API does is serve a JSON file with upcoming public holidays. I wrote a ramshackle little Python script that grabs the file, works out what the next holiday is, and prints it out.
Crucially, it also prints bunting if, according to the JSON, the occasion calls for it.
Banking crisis
So, here’s the quite-ordinary code for this demonstration2:
bank.py
import httpxfrom datetime import date# Fetch bank holidaysresp = httpx.get("https://www.gov.uk/bank-holidays.json")events = resp.json()["england-and-wales"]["events"]for event in events: event["date"] = date.fromisoformat(event["date"])# Find next holidaytoday = date.today()future = [event for event in events if event["date"] >= today]next= future[0]# Print messageis_today =next["date"] == todayneeds_bunting =next["bunting"]if is_today: when ="today!"else: when =f"on {next['date'].strftime('%d %B %Y')}"str_out =f"{next['title']} is {when}"if needs_bunting: str_out = str_out +" ▼▽▼▽▼▽"print(str_out)
Summer bank holiday is on 25 August 2025 ▼▽▼▽▼▽
Very simple. Does the job3. But if we want to use the script, maybe we’ll have to clone a repo containing the script, change directory, activate the virtual environment and only then run the script. Every time we want to use it.
Wouldn’t it be better if you could just call it quickly from the command line when you need it? Just type bank.py and get a quick response?
You can start by adding a block at the top of your file to declare dependencies. Kind of like bundling a pyproject.toml inside your script5. No need to worry about a separate dependencies file, lockfile or virtual environment.
To add your dependencies, use uv add with the --script option in the form:
uv add httpx datetime --script bank.py
Which will add the following section at the top of our bank.py script, including the Python requirement:
And there you have it: all the information needed to run this file in an isolated manner. You’ll be able to run uv run bank.py from your terminal without any additional files required.
But wait! There’s more. How boring, how painful to have to write the uv run bit. That’s six characters6. And you’ll have to provide the full path to the script, yikes.
Instead, you can add a shebang7 at the top of the file to make it executable:
#!/usr/bin/env -S uv run
Note the unusual bit on the end… aha! uv again! The -S uv run bit asks the interpreter to let good ol’ uv handle the whole operation.
Finally, you can change the mode (chmod) of the file by adding (+) executable (x) status. Then put the file where you terminal will look if you type the name of the file.
chmod +x bank.pymv bank.py ~/.local/bin
Then you can merely call bank.py from the terminal to know when the next bank holiday is8.
#!/usr/bin/env -S uv run# /// script# requires-python = ">=3.12"# dependencies = [# "datetime",# "httpx",# ]# ///import httpxfrom datetime import date# Fetch bank holidaysresp = httpx.get("https://www.gov.uk/bank-holidays.json")events = resp.json()["england-and-wales"]["events"]for event in events: event["date"] = date.fromisoformat(event["date"])# Find next holidaytoday = date.today()future = [event for event in events if event["date"] >= today]next= future[0]# Print messageis_today =next["date"] == todayneeds_bunting =next["bunting"]if is_today: when ="today!"else: when =f"on {next['date'].strftime('%d %B %Y')}"str_out =f"{next['title']} is {when}"if needs_bunting: str_out = str_out +" ▼▽▼▽▼▽"print(str_out)
Next, more important goal: write something similar to help me know what bin day it is (we alternate fortnightly between refuse and recycling). I saved a neighbour from getting erroneously binfluenced recently, so this is dear to my heart.
And thus my 182-post streak without running Python comes to an end.↩︎
Please feel free to critique my Python code. How could it possibly be worse than my R code?↩︎
As a more frequent R user: 0 days since last zero-indexing mistake.↩︎
Perhaps you have been living under a rock where uv rays have not struck you. uv is all the rage for doing all-the-Python-setup-things-that-make-Python-setup-a-headache. And it’s super fast. Because Rust. Because carcinisation is coming for all of us.↩︎
Basically says ‘if I’m called, execute me like a little standalone program’.↩︎
I swear I saw a blog or video somewhere that showed how to just type the filename without the extension, like bank. Tell me if you know what I mean.↩︎
I’m losing my mind becuase I’ve discovered recently that the pronunciation of ‘gist’ is another ‘how do you pronounce gif?’ situation. To be clear, it should be pronounced ‘gist’.↩︎