Automation is not a grand thing. It is a script that does a whole job on its own, start to finish, with nobody typing anything, so a computer can run it while you sleep. You already have the pieces. This part joins them into one program that summarises your spending, then hands that program to your operating system to run on a schedule.
main function, guard it with if __name__ == '__main__': so the file can be both imported and run, make sure it needs no typed input, then schedule it with cron on macOS or Linux, or Task Scheduler on Windows. That is the whole idea, and everything below fills it in.This part pulls together files from Part 11, JSON from Part 16, and dates from Part 13. You need Python 3 and about thirty minutes. The scheduling piece touches your operating system, so the exact command differs by platform and both are shown.
The line that makes a file runnable
You have run scripts since Part 2, but there is one idiom every real script uses that we have skipped until now. It looks strange and it earns its place:
def main():
print('summary would run here')
if __name__ == '__main__':
main()Python sets a hidden variable __name__ for every file. When you run a file directly, its __name__ is the text '__main__', so the guarded line is true and main runs. When another file imports this one, __name__ is the module’s name instead, the guard is false, and main does not fire. That single check lets a file be both a runnable script and a reusable module, and you will see it at the bottom of almost every Python program you meet.
| How the file is used | Value of __name__ | Does main() run? |
|---|---|---|
| You run it directly | '__main__' | Yes |
| Another file imports it | The module name | No |
Table 1. Why the main guard runs your code only when the file is the one being run.
A script that summarises the spending
Here is the first real automation for the tracker. It loads the saved expenses, groups them by month, and prints a total for each. Because the project stamped each expense with a date back in Part 13, grouping is a matter of reading the first seven characters of the date, the year and month. It takes no input, which is what makes it safe to run unattended.
import json
from collections import defaultdict
def load(path='expenses.json'):
with open(path) as f:
return json.load(f)
def monthly_totals(expenses):
totals = defaultdict(float)
for e in expenses:
month = e['date'][:7]
totals[month] += e['amount']
return totals
def main():
expenses = load()
totals = monthly_totals(expenses)
for month in sorted(totals):
print(f'{month} {totals[month]:>10.2f}')
if __name__ == '__main__':
main()2026-06 210.00 2026-07 160.50
The defaultdict(float) is a small convenience from the standard library: a dictionary that starts any new key at 0.0, so you can add to a month’s total without checking whether you have seen it before. Everything else is the file and JSON work you already know, arranged so the program runs once and finishes with a clear result on screen.
Handing the script to a scheduler
A script that runs itself is only automated once something triggers it without you. That something is a scheduler built into your operating system. On macOS and Linux it is cron; on Windows it is Task Scheduler. A cron entry is one line: five time fields, then the command. This one runs the summary at nine every morning:
0 9 * * * /home/asha/tracker/.venv/bin/python /home/asha/tracker/monthly_summary.py
You add it by running crontab -e, pasting the line, and saving. On Windows, you open Task Scheduler, create a basic task, set a daily trigger, and point it at your Python and script. Same idea, different door.
Figure 1. The scheduler fires on a timer, the script runs unattended, and you get output without lifting a finger.
FileNotFoundError: [Errno 2] No such file or directory: 'expenses.json'
expenses.json is not found. The fix is to use full paths everywhere, both to the venv’s Python and to your data files, exactly as the cron line above does. Relative paths are the single most common reason a working script breaks once scheduled.Keep the first automation small
There is a temptation, once you can schedule things, to reach for a heavy workflow tool with dashboards and retries for what is really a nightly one liner. Resist it at this stage. A plain script triggered by cron is enough for the vast majority of small automations, and it is far easier to understand and fix at two in the morning than a framework you half configured. Reach for the big tools when you genuinely have many dependent steps that must coordinate, not before.
Two habits make an unattended script trustworthy. Make it safe to run twice, so a repeat run does not double count or corrupt data, which is called being idempotent. And have it write a short line to a log file each time it runs, so when you wonder later whether it ran at all, the answer is written down. Those two habits cost a few lines and save whole evenings.
if __name__ == '__main__': do?" A clean answer: it checks whether the file is being run directly, in which case __name__ is '__main__' and the block runs, or being imported, in which case it does not. It lets a file work as both a runnable script and an importable module. Being able to explain it plainly is a small but reliable signal that you have written real scripts, not just snippets.greet function returning the text ready to run, then prints it only when the file is run directly, using the main guard. Expected output when you run the file:
ready to run
Show solution
def greet():
return 'ready to run'
if __name__ == '__main__':
print(greet())Run the file and it prints; import it into another file and the print stays silent, because the guard is only true for the file you run directly.
Questions about automating your scripts
Do I always need the main guard? Not for a one off you run by hand, but get into the habit. The moment another file imports yours, the guard is what stops your whole script running as a side effect.
Why use the full path to python in cron? So the scheduler uses your project’s virtual environment, with its installed packages, instead of some other Python that lacks them. The path to .venv/bin/python pins the exact interpreter.
How do I pass an input to a scheduled script? You do not type it; you bake it in, read it from a file, or accept it as a command line argument through sys.argv. Unattended means no prompts.
Where does the output go when cron runs it? Nowhere visible by default. Redirect it to a file in the cron line, or have the script write its own log, so you can see what happened.
Is Windows Task Scheduler as capable as cron? For this purpose, yes. It triggers a program on a schedule just the same. The concepts match; only the interface differs.
Passing an argument on the command line
Unattended does not mean fixed forever. A scheduled script often needs one small piece of input, like which month to summarise, and you supply it on the command line rather than by typing at a prompt. Python hands you the words from the command line in a list called sys.argv:
import sys
def main():
if len(sys.argv) > 1:
month = sys.argv[1]
else:
month = 'all'
print(f'summarising: {month}')
if __name__ == '__main__':
main()python monthly_summary.py 2026-07
summarising: 2026-07
The list sys.argv holds the words you typed. The first entry is the script name itself, and everything after it is an argument you passed. Checking the length before reading sys.argv[1] avoids an IndexError when no argument is given, and a sensible default keeps the script working whether or not you supply one.
| Entry | Holds |
|---|---|
sys.argv[0] | The script name itself |
sys.argv[1] | The first argument you passed |
sys.argv[2] | The second argument, and so on |
Table 2. What the command line arguments land in when your script starts.
One thing to remember: every entry in sys.argv is text, even a number, so convert with int() or float() before doing maths on it, the same lesson as reading from a file. When a script grows past a couple of options, the standard argparse module gives you named flags, help text, and validation for free, and it is the natural next step once bare sys.argv starts to feel fiddly.
if __name__ == '__main__':, group and summarise saved data, and schedule the whole thing with cron or Task Scheduler while avoiding the relative path trap that breaks scheduled jobs. Your tracker produces a monthly summary on its own. In Part 18 you make it trustworthy, with debugging habits and a couple of small tests.


DrJha