Part-5: Work with Data Files in Python
Build a Python inventory manager that turns JSON server data into CSV reports.
Introduction
30 Second Summary
When the same server information lives in two files, one update can leave the other copy out of date. Soon, nobody knows which version to trust.
In this project, you will build a command-line server inventory manager using Python's standard library. The manager turns an updated JSON inventory into a CSV report from one authoritative source.
What You'll Build
You will run the inventory manager to see an updated server status in the terminal, followed by a matching CSV report generated from the same data.
By the end of this project, you'll have:
- A runnable data-structure comparison that prints a fixed server record from a tuple. It also shows two sorted unique IP addresses from a set.
- Readable JSON updates that load nested inventory data into Python dictionaries. The saved file shows the changed status with four-space indentation.
- A resilient CSV pipeline that reads rows by header name. It handles a missing JSON file with a clear message. Its final report comes from a single source of truth.
- Secret Mission: Filter the inventory into a region-specific CSV report containing only the two eu-west servers.
Are there any prerequisites?
This project uses a standard Linux environment with a standard Linux text editor (like nano, vim, or gedit), the standard Linux terminal, and the installed python3. You should already be comfortable with Python fundamentals covering data structures, control flow, functions, exceptions, text files, modules, pip, and virtual environments.
Before We Start
This opening checkpoint locks in what your inventory manager reads, updates, and produces before the hands-on work begins.
Verify Your Data Tools
Every later script depends on the same local interpreter being able to run Python commands. A quick check confirms that foundation before you create any project files.
Your standard Linux environment includes access to Python. You will use a standard Linux text editor (like nano, vim, or gedit) to edit files and the standard Linux terminal to run commands.
In this step, get ready to:
- Confirm that python3 returns a Python 3.x.x version line.
- Confirm that the built-in csv and json modules load successfully.
Check the installed interpreter
A standard Linux text editor (like nano, vim, or gedit) is where you create and edit the project files. The standard Linux terminal runs the commands that verify your setup.
- Open the application launcher for your standard Linux environment.
- Search for Terminal.
- Select the terminal application from the results.
- Confirm that you can see a command prompt in the standard Linux terminal.
- Open the application launcher again.
- Search for Text Editor.
- Select a standard Linux text editor (like nano, vim, or gedit).
- Keep the standard Linux terminal available for the checks below.
python3 --version
What does this command check?
The python3 command starts the system-installed interpreter. The --version option asks it to display its version.
You'll see a line shaped like Python 3.x.x.
That first check is complete: the standard Linux terminal can reach your Python interpreter.
Not Seeing a Python Version?
Compare your command with the block above. Make sure it uses two hyphens before version.
Ask for help with your version check.
Confirm the built-in data modules
JSON stores structured data. CSV stores table-like rows.
Python's standard library includes the json and csv modules. This project needs no pip installation.
Before you run this check, what message do you expect if both modules are ready?
- Confirm that Python can load both modules by running this command:
python3 -c "import csv, json; print('Data tools ready')"
How Does This Check Work?
- The -c option tells Python to run the code inside the quotes.
- The import csv, json statement confirms that both modules can be loaded.
- The print('Data tools ready') call provides a visible success message after the imports complete.
You'll see Data tools ready in the standard Linux terminal.
You're ready for the data work: Python can load both modules from its standard library.
Success Message Missing?
Compare the import names, comma, quotes, and parentheses with the command above. Python must load both modules before it reaches the print call.
Ask for help with the module check.
Your Python data tools are ready. Next, you'll model fixed server details and remove duplicate IP addresses.
Use Tuples and Sets for Server Data
Your Python data tools are ready. The inventory manager now needs structures that show how each piece of server data should behave.
A list keeps every value while allowing changes. A tuple holds fixed server facts.
A set keeps duplicate-free IP addresses.
In this step, get ready to:
- Model fixed server facts with a tuple.
- Remove duplicate IP addresses with a set.
- Compare list, tuple, and set choices through short exercises.
Build a fixed server record
A tuple works like a printed asset label whose fields stay fixed. A set works like a guest list that keeps each entry once.
Three structures, three intentions
- Use a list when order matters while items may change.
- Use a tuple for one fixed record such as a server name with its address and region.
- A tuple is immutable. Its individual items cannot be reassigned.
- Use a set when uniqueness matters more than order.
A standard Linux text editor (like nano, vim, or gedit) is where you create the script. The standard Linux terminal runs the saved file.
- Open the application launcher for your standard Linux environment.
- Search for Text Editor.
- Select a standard Linux text editor (like nano, vim, or gedit) from the results.
- Create a blank document with your editor's new-file command.
- Add the server record plus duplicate IP data by pasting this code into the blank document:
server = ("web-01", "10.0.0.10", "eu-west")
log_ips = ["10.0.0.10", "10.0.0.20", "10.0.0.10"]
unique_ips = set(log_ips)
print(server)
print(sorted(unique_ips))
print(f"Unique IPs: {len(unique_ips)}")
What does this code do?
- The server tuple stores one fixed three-field record.
- The log_ips list deliberately contains one repeated address.
- The set(log_ips) call keeps each address once.
- The sorted(unique_ips) call gives the unordered set a predictable display order.
- The len(unique_ips) call counts the two unique addresses.
- Save the document as tuples_sets.py in your home folder.
- Switch back to the standard Linux terminal from the previous step.
Before you run the script, decide which repeated IP you expect the set to remove.
- Run the saved server-data example with this command:
python3 tuples_sets.py
What should you see?
- The first line shows ('web-01', '10.0.0.10', 'eu-west') as the server tuple.
- The second line shows ['10.0.0.10', '10.0.0.20'] as the sorted unique addresses.
- The final line shows Unique IPs: 2.
🙋♀️ Script not running?
Check that your standard Linux text editor saved the file in your home folder. Confirm that its filename is exactly tuples_sets.py.
Compare every quote plus bracket with the example if Python reports a syntax problem.
Help me debug my tuples and sets script.
Solid start. Your script now expresses fixed facts plus unique IPs.
✔️ Awesome, I've got everything!
- Keep tuples_sets.py saved in your standard Linux text editor.
ⓧ I'd like to double check the full code
The complete file contains the fixed record plus the duplicate-removal logic.
server = ("web-01", "10.0.0.10", "eu-west")
log_ips = ["10.0.0.10", "10.0.0.20", "10.0.0.10"]
unique_ips = set(log_ips)
print(server)
print(sorted(unique_ips))
print(f"Unique IPs: {len(unique_ips)}")
How is the file organized?
The first three lines create the record plus the unique-address collection. The last three lines print predictable evidence that both structures work.
Practice choosing structures
Small comparisons make each structure's purpose easier to recognize. Try each exercise before checking the expected result.
- Use positions 0 and 2 from server = ("db-01", "10.0.1.10", "us-east") to form a sentence.
- Deduplicate ['eu-west', 'us-east', 'eu-west'] with a set.
- Sort the resulting region values.
- Decide whether 10.0.0.99 belongs to the unique IP set from your script.
✔️ I'll try it first
- Complete all three exercises before opening the solution tab.
ⓧ Let me check my work
- Tuple positions 0 and 2 produce db-01 is in us-east.
- The sorted region set displays ['eu-west', 'us-east'].
- The membership check displays 10.0.0.99 found: False.
Avoid common tuple and set mistakes
Python uses small punctuation details to distinguish these structures. Two patterns prevent confusing results in later inventory scripts.
Two syntax details to remember
- Parentheses alone do not create a one-item tuple.
- Write ("web-01",) because the trailing comma creates the tuple.
- The value {} creates an empty dictionary.
- Use set() to create an empty set.
- Lists suit changeable sequences.
- Tuples suit fixed records.
- Sets suit unique values.
- Switch back to the standard Linux terminal from earlier.
Before the final check, decide which output line should prove that the duplicate IP was removed.
- Verify the final tuples_sets.py file by running this command:
python3 tuples_sets.py
Your final result
- You should see ('web-01', '10.0.0.10', 'eu-west') first.
- You should see exactly two addresses in ['10.0.0.10', '10.0.0.20'] next.
- You should see Unique IPs: 2 last.
🙋♀️ Output does not match?
Confirm that log_ips contains 10.0.0.10 twice. Confirm that unique_ips is created with set(log_ips).
Check that the second print uses sorted(unique_ips) so the output order stays predictable.
Help me fix unexpected tuple or set output.
Your server data now distinguishes fixed records from unique collections. Next, you'll load a complete inventory from JSON.
Read, Update, and Write JSON
Your tuple from the previous step represented one fixed server. A complete inventory needs nested fields for several servers.
In this step, you'll use JSON to load a nested inventory into Python. Then you'll persist one status change in a readable file.
In this step, get ready to:
- Create a structured server inventory in JSON.
- Load the inventory into Python.
- Persist an updated status as readable JSON.
Load JSON as Python data
A JSON file works like a labeled storage cabinet. Its braces and brackets show tools where each piece of inventory data belongs.
How does JSON map to Python?
A JSON object becomes a Python dictionary. A JSON array becomes a Python list.
Deserialization turns stored text into Python objects. The json.load(file) call performs that conversion for an open file.
- In a standard Linux text editor (like nano, vim, or gedit), create servers.json beside tuples_sets.py.
- Paste this starter inventory into servers.json:
{
"inventory_name": "training-lab",
"servers": [
{"name": "web-01", "ip": "10.0.0.10", "cpu": 4, "ram_gb": 8, "region": "eu-west", "status": "online"},
{"name": "api-01", "ip": "10.0.0.20", "cpu": 8, "ram_gb": 16, "region": "eu-west", "status": "maintenance"},
{"name": "db-01", "ip": "10.0.1.10", "cpu": 8, "ram_gb": 32, "region": "us-east", "status": "online"}
]
}
How is this inventory structured?
The top-level object holds the inventory name. It also holds the server list.
The list contains three server objects. Each object keeps one server's name, address, capacity, region, and status together.
- Save servers.json.
Your standard Linux text editor now shows servers.json as a saved file beside tuples_sets.py.
The inventory is still stored text. A loader turns that text into dictionaries and lists your existing Python skills can use.
- Create read_json.py beside servers.json in your standard Linux text editor.
- Paste this loader into read_json.py:
import json
with open("servers.json", encoding="utf-8") as file:
inventory = json.load(file)
print(f"Inventory: {inventory['inventory_name']}")
print(f"First server: {inventory['servers'][0]['name']}")
What does this code do?
- The import json line makes Python's JSON tools available.
- The with open(...) block reads servers.json using UTF-8 text encoding.
- The inventory variable holds the decoded dictionary returned by json.load(file).
- The final lines read the inventory name and the first server name from the nested structure.
- Save read_json.py.
- Load the inventory from the standard Linux terminal by running this command:
python3 read_json.py
What should you see?
The standard Linux terminal prints Inventory: training-lab followed by First server: web-01.
Good progress. Your JSON inventory is now available as Python data that the later scripts can update.
🙋♀️ Inventory not loading?
- Check that every JSON name and string uses double quotes.
- Check each object for a missing comma.
- Confirm read_json.py is saved beside servers.json.
- Use json.load(file) for the open file object. The json.loads() function expects a string.
Help me debug my JSON loader.
The loaded structure also gives you three short lookup exercises. Make each change temporarily so the final file can return to its focused example.
- Use len(inventory["servers"]) in read_json.py to produce Servers: 3.
- Use inventory["servers"][0]["region"] to produce First region: eu-west.
- Loop over inventory["servers"] to print web-01, api-01, and db-01 on separate lines.
- Save read_json.py after each exercise.
- Rerun the script after each exercise.
✔️ Awesome, I've got everything!
Your lookups reached the server count, first region, and individual server names.
ⓧ I'd like to double check the full code
Compare your finished loader with the complete read_json.py:
import json
with open("servers.json", encoding="utf-8") as file:
inventory = json.load(file)
print(f"Inventory: {inventory['inventory_name']}")
print(f"First server: {inventory['servers'][0]['name']}")
- Restore read_json.py to the complete version shown in the double-check tab.
- Save read_json.py.
- Confirm the restored loader by running:
python3 read_json.py
Restored output
You should again see Inventory: training-lab followed by First server: web-01. This confirms the saved loader matches the target version.
Update and pretty-print JSON
Serialization turns Python objects into stored text. Pretty-printing adds line breaks and spacing so people can inspect the structure.
An indent is the repeated spacing that shows how deeply a value is nested. This script uses four spaces for each level.
- Create write_json.py beside servers.json in your standard Linux text editor.
- Paste this update script into write_json.py:
import json
with open("servers.json", encoding="utf-8") as file:
inventory = json.load(file)
inventory["servers"][0]["status"] = "maintenance"
with open("servers.json", "w", encoding="utf-8") as file:
json.dump(inventory, file, indent=4)
print("web-01 status: maintenance")
What does this code do?
- The first file block loads the complete inventory before any stored data is replaced.
- The assignment changes the first server's status value to maintenance.
- The second file block opens servers.json in write mode.
- The json.dump(inventory, file, indent=4) call serializes the updated dictionary with readable indentation.
- Save write_json.py.
Before you run the update, predict which status the script prints for web-01.
- Update the stored inventory by running:
python3 write_json.py
What just happened?
The standard Linux terminal prints web-01 status: maintenance.
That's the first persistent inventory update complete. The new status now lives on disk.
🙋♀️ Status not updating?
- Compare the nested brackets in the status assignment with the complete code.
- Confirm servers.json still contains the starter inventory before rerunning the script.
- Confirm the read block appears before the file is opened with mode w.
Help me debug my JSON update script.
Confirm the saved change
The terminal message confirms the script reached its final line. The saved file confirms serialization preserved the complete inventory.
Before you inspect the file, predict what json.dump() preserves besides the changed status.
- Switch to the servers.json tab in Xed.
- Find the server object containing "name": "web-01".
- Confirm its status now reads "status": "maintenance".
- Check the spacing at each nested level.
You'll see web-01 in maintenance. You'll also see api-01 still in maintenance.
The nested fields use four-space indentation. The other server values remain intact.
Why load before writing?
Opening an existing file with mode w truncates its current contents. Loading the inventory first keeps the source data available for the update.
Change the Python dictionary before calling json.dump(..., indent=4). That sequence protects the inventory while producing readable JSON.
✔️ Awesome, I've got everything!
Your JSON loader and writer are saved. The inventory now contains the updated status with readable indentation.
ⓧ I'd like to double check the full code
Your existing tuples_sets.py remains unchanged:
server = ("web-01", "10.0.0.10", "eu-west")
log_ips = ["10.0.0.10", "10.0.0.20", "10.0.0.10"]
unique_ips = set(log_ips)
print(server)
print(sorted(unique_ips))
print(f"Unique IPs: {len(unique_ips)}")
Your complete read_json.py should match this file:
import json
with open("servers.json", encoding="utf-8") as file:
inventory = json.load(file)
print(f"Inventory: {inventory['inventory_name']}")
print(f"First server: {inventory['servers'][0]['name']}")
Your complete write_json.py should match this file:
import json
with open("servers.json", encoding="utf-8") as file:
inventory = json.load(file)
inventory["servers"][0]["status"] = "maintenance"
with open("servers.json", "w", encoding="utf-8") as file:
json.dump(inventory, file, indent=4)
print("web-01 status: maintenance")
Your updated servers.json should match this file:
{
"inventory_name": "training-lab",
"servers": [
{
"name": "web-01",
"ip": "10.0.0.10",
"cpu": 4,
"ram_gb": 8,
"region": "eu-west",
"status": "maintenance"
},
{
"name": "api-01",
"ip": "10.0.0.20",
"cpu": 8,
"ram_gb": 16,
"region": "eu-west",
"status": "maintenance"
},
{
"name": "db-01",
"ip": "10.0.1.10",
"cpu": 8,
"ram_gb": 32,
"region": "us-east",
"status": "online"
}
]
}
Your inventory now loads into Python and keeps deliberate changes in readable JSON. Next up, you'll turn server rows into a flat CSV summary.
Read CSV Rows and Write a Summary
Your JSON inventory now keeps nested server details in a readable structure. The next report needs a flatter shape that people or tools can scan row by row.
A CSV source provides that tabular view. Keeping a second inventory file also lets you test whether both copies still agree.
In this step, get ready to:
- Create a CSV source containing three server rows.
- Read CSV fields by their header names.
- Write a three-column server summary.
Create the CSV source and summary script
A header names the columns in a CSV file. Each later row stores one record using the same column order.
How does Python read CSV rows?
The csv.DictReader reader uses the first row as field names. Each later row becomes a dictionary whose keys match those names.
The csv.writer writer accepts selected values for a new file. This lets one detailed source produce a smaller report.
- In a standard Linux text editor (like nano, vim, or gedit), create a new text file named servers.csv beside servers.json.
- Paste the following data into servers.csv:
name,ip,cpu,ram_gb,region,status
web-01,10.0.0.10,4,8,eu-west,online
api-01,10.0.0.20,8,16,eu-west,maintenance
db-01,10.0.1.10,8,32,us-east,online
What does this data contain?
- The first row defines six field names for each server.
- The next three rows store the values for web-01, api-01, and db-01.
- The repeated column order gives the CSV reader a predictable structure.
- Save servers.csv.
Your saved file contains one header row followed by three server rows.
Rows not lining up?
Check that every server row contains six comma-separated values. Keep the header on the first line.
Make sure your standard Linux text editor saved the file as servers.csv in the same location as your other project files.
Help me check the structure of my servers.csv file.
- In your standard Linux text editor, create a new text file named csv_basics.py beside servers.csv.
- Paste the following code into csv_basics.py:
import csv
with open("servers.csv", newline="", encoding="utf-8") as source, open("server_summary.csv", "w", newline="", encoding="utf-8") as target:
reader = csv.DictReader(source)
writer = csv.writer(target)
writer.writerow(["name", "region", "status"])
for row in reader:
writer.writerow([row["name"], row["region"], row["status"]])
print(row["name"], row["status"])
What does this code do?
- The script opens servers.csv as its source. It opens server_summary.csv as its output.
- The newline="" setting lets the CSV module control newline handling.
- The writer creates a header containing name, region, and status.
- The loop copies those three fields from each row. It also prints each server name with its status.
- Save csv_basics.py.
Before you run the script, predict whether web-01 will appear as online or maintenance.
- Switch back to the standard Linux terminal.
- Run the summary script with this command:
python3 csv_basics.py
What does this run check?
Python executes csv_basics.py against the saved CSV source. The script prints each source status while creating server_summary.csv.
You'll see web-01 online, api-01 maintenance, and db-01 online on separate lines.
- Return to servers.json in your standard Linux text editor.
- Find the status value for web-01.
The JSON file says maintenance. The CSV output says online.
That mismatch is intentional. Two separately maintained inventory files can drift apart even when both files remain valid.
Script not producing three lines?
Check that servers.csv is saved beside csv_basics.py. Confirm that the source file still contains its header row.
Compare the indentation beneath for row in reader: with the full-code reference later in this step.
Help me debug my CSV reader output.
Practise reading and filtering rows
Each row dictionary gives you named fields for filtering or calculations. CSV values arrive as strings, so numeric fields need conversion before arithmetic.
Use the header names
Use row["region"] for a region check. Use int(row["ram_gb"]) before adding RAM values.
For a filtered report, increase the counter only after the writer saves a matching row.
- Rewrite the temporary loop in csv_basics.py to print server names whose region is eu-west.
- Save csv_basics.py.
- Run the region exercise with this command:
python3 csv_basics.py
What does the region check prove?
The condition reads the region field by its header name. Only matching row dictionaries reach the print statement.
You'll see web-01 and api-01 on separate lines.
- Rewrite the temporary exercise to print the integer total of the ram_gb column.
- Save csv_basics.py.
- Run the RAM exercise with this command:
python3 csv_basics.py
What does the RAM check prove?
The int() conversion turns each RAM field into a number. The converted values can contribute to one numeric total.
You'll see Total RAM: 56 GB.
- Rewrite the temporary exercise as an online-only report that prints its number of written rows.
- Save csv_basics.py.
- Run the online-server exercise with this command:
python3 csv_basics.py
What does the row count prove?
The counter tracks rows that pass the online filter. Its final value confirms how many records the writer selected.
You'll see Online servers written: 2.
Exercise output looks different?
Check that your field names match the lowercase header values in servers.csv.
For the RAM total, confirm that every ram_gb value passes through int() before addition.
Help me debug my CSV row exercise.
Restore and verify the summary
The exercises temporarily changed your script. The final version needs to recreate the original three-column summary from all three source rows.
Avoid two CSV mistakes
Keep newline="" on CSV file handles so the CSV module controls newline handling.
Convert numeric strings with int() before arithmetic. The default CSV reader returns field values as strings.
✔️ Awesome, I've got everything!
- Confirm servers.csv still contains the original three server rows.
- Confirm csv_basics.py has been restored to the three-column summary script.
ⓧ I'd like to double check the full code
Compare your complete servers.csv with this reference.
name,ip,cpu,ram_gb,region,status
web-01,10.0.0.10,4,8,eu-west,online
api-01,10.0.0.20,8,16,eu-west,maintenance
db-01,10.0.1.10,8,32,us-east,online
What should this source contain?
This file contains the six-column header followed by the original three server rows. Its web-01 status remains online.
Compare your complete csv_basics.py with this reference.
import csv
with open("servers.csv", newline="", encoding="utf-8") as source, open("server_summary.csv", "w", newline="", encoding="utf-8") as target:
reader = csv.DictReader(source)
writer = csv.writer(target)
writer.writerow(["name", "region", "status"])
for row in reader:
writer.writerow([row["name"], row["region"], row["status"]])
print(row["name"], row["status"])
What should this script contain?
This restored script reads all three source rows. It writes only the name, region, and status fields to the summary.
- Save servers.csv.
- Save csv_basics.py.
Before the final run, predict which file determines the status printed for web-01.
- Return to Xfce Terminal.
- Run the restored summary script with this command:
python3 csv_basics.py
What does the final run create?
The restored script reads every row from servers.csv again. It recreates server_summary.csv with three selected columns.
You'll again see web-01 online, api-01 maintenance, and db-01 online on separate lines.
- In Xed, open the generated server_summary.csv file.
- Compare its contents with this reference:
name,region,status
web-01,eu-west,online
api-01,eu-west,maintenance
db-01,us-east,online
What does the summary show?
The summary keeps one row for each server. It reduces the six source columns to the three fields needed for this report.
- Return to servers.json in Xed.
- Find the saved status for web-01.
You can now see the problem clearly. servers.json records web-01 as maintenance, while server_summary.csv records it as online.
Summary file missing or incorrect?
Confirm that servers.csv and csv_basics.py are saved in the same location.
Compare the restored loop with the full-code tab. Check the indentation of both statements inside the loop.
Help me fix my generated CSV summary.
You have proved that separate inventory copies can drift out of sync. Next, you'll generate the report directly from the JSON inventory so one authoritative source controls every status.
Build the Inventory Manager
Your previous CSV exercise exposed a stale copy. The generated report still says web-01 is online.
The JSON inventory records web-01 as maintenance. That mismatch is the problem this step solves.
A single source of truth is the one authoritative file from which other reports are generated. Your manager uses servers.json to update the inventory and generate the report.
In this step, get ready to:
- Handle a missing source file without a traceback.
- Persist the api-01 status update in servers.json.
- Generate a six-column server_summary.csv from the updated JSON inventory.
Handle the missing-file path
A missing inventory file would normally stop the script. A try/except structure catches FileNotFoundError and prints a useful message.
- Return to your standard Linux text editor from earlier.
- Create a blank document in the editor.
You should see an empty editing area ready for the new script.
- Save the new document as inventory_manager.py in the same folder as servers.json.
- Add the imports and missing-file handler by entering this code:
import csv
import json
input_file = "servers.json"
try:
with open(input_file, encoding="utf-8") as file:
inventory = json.load(file)
except FileNotFoundError:
print(f"Error: {input_file} was not found.")
What does this code do?
- The csv and json imports make both standard-library modules available.
- The input_file variable keeps the authoritative inventory path in one place.
- The try block attempts to open and deserialize the inventory.
- The except FileNotFoundError block replaces a missing-file crash with a useful message.
- Save inventory_manager.py.
Your standard Linux text editor should now show inventory_manager.py as the current file.
- Change the input_file value from servers.json to missing_servers.json.
- Save inventory_manager.py.
Before you run the script, consider whether it shows a traceback or your handled message.
- Test the missing-file path in the standard Linux terminal by running:
python3 inventory_manager.py
What does this run prove?
The command runs your inventory manager while its source path points to a file that does not exist. This deliberately triggers the FileNotFoundError handler.
You should see Error: missing_servers.json was not found. without a traceback. This is the intended missing-file result.
Seeing a traceback?
Check that except FileNotFoundError: aligns with try:. Confirm that the print line is indented inside the exception block.
Help me fix the missing-file handler.
Update and save the inventory
The successful path belongs in an else block. It runs only after servers.json loads successfully.
- Change the input_file value back to servers.json.
The source path now points to the inventory file that already exists beside your script.
- Add this else block below the exception handler:
else:
servers = inventory["servers"]
for server in servers:
if server["name"] == "api-01":
server["status"] = "online"
print("Updated api-01 to online.")
How does the update work?
- The servers variable holds the list loaded from the inventory.
- The loop checks each server dictionary for the name api-01.
- The matching dictionary receives the status online.
- The print statement exposes the in-memory update in the terminal.
- Save inventory_manager.py.
- Run the updated script by running:
python3 inventory_manager.py
What does this run check?
This run loads the real inventory and follows the successful else path. The terminal message confirms that the matching dictionary changed in memory.
You should see Updated api-01 to online..
Missing the update message?
Confirm that else: aligns with except FileNotFoundError:. Check that the server loop is indented inside the else block.
Help me debug the status update.
The dictionary update must be serialized to survive after the script exits. Writing the complete inventory preserves every server while changing the selected status.
- Insert this block above the existing print("Updated api-01 to online.") line:
with open(input_file, "w", encoding="utf-8") as file:
json.dump(inventory, file, indent=4)
file.write("\n")
How is the inventory saved?
- Opening input_file in write mode replaces its previous contents with the updated inventory.
- The json.dump() call serializes the Python data with four-space indentation.
- The final newline keeps the saved text file clean at the end.
- Save inventory_manager.py.
- Persist the updated status by running:
python3 inventory_manager.py
What changes during this run?
The script loads servers.json and updates api-01 in memory. It then writes the complete inventory back to the same file.
You should still see Updated api-01 to online. in the terminal.
- Use your standard Linux text editor's file chooser to open servers.json from the folder containing inventory_manager.py.
You should see api-01 with the status online. The nested JSON remains visibly indented.
Status still says maintenance?
Check that the write block uses input_file and sits inside the else block. Confirm that you saved inventory_manager.py before running it.
Help me fix the JSON save step.
Generate and verify the CSV summary
The manager can now derive a flat report from the updated inventory. Every report row comes from the same servers list that was saved to JSON.
- Find the existing print("Updated api-01 to online.") line in inventory_manager.py.
- Replace that line with this CSV-writing block and both completion messages:
with open("server_summary.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(["name", "ip", "cpu", "ram_gb", "region", "status"])
for server in servers:
writer.writerow([
server["name"],
server["ip"],
server["cpu"],
server["ram_gb"],
server["region"],
server["status"],
])
print("Updated api-01 to online.")
print(f"Wrote {len(servers)} servers to server_summary.csv.")
How is the report generated?
- The writer creates server_summary.csv with a six-column header.
- The loop writes one row for every server loaded from servers.json.
- The first message confirms the status update.
- The second message reports the number of CSV rows written.
- Save inventory_manager.py.
Before the final run, consider which statuses the report should contain after reading the JSON source.
- Run the complete inventory pipeline by running:
python3 inventory_manager.py
What does the final run prove?
This run updates the authoritative JSON inventory and regenerates the CSV report from the same loaded data. The row count confirms that all three servers reached the report.
You should see Updated api-01 to online. followed by Wrote 3 servers to server_summary.csv..
- Use Xed's file chooser to display servers.json from the folder containing the manager.
You should see web-01 in maintenance. You should see api-01 and db-01 in online.
- Use Xed's file chooser to display server_summary.csv from the same folder.
You should see the six-column header name,ip,cpu,ram_gb,region,status. The three report rows should contain the same statuses as servers.json.
Report missing or still stale?
Confirm that the CSV block is indented inside else and uses servers as its row source. Run inventory_manager.py instead of csv_basics.py.
Help me debug the generated CSV report.
✔️ Awesome, I've got everything!
Your saved inventory_manager.py now handles a missing source and updates the JSON inventory. It also generates the six-column CSV report.
ⓧ I'd like to double check the full code
Compare your saved inventory_manager.py with this complete version.
import csv
import json
input_file = "servers.json"
try:
with open(input_file, encoding="utf-8") as file:
inventory = json.load(file)
except FileNotFoundError:
print(f"Error: {input_file} was not found.")
else:
servers = inventory["servers"]
for server in servers:
if server["name"] == "api-01":
server["status"] = "online"
with open(input_file, "w", encoding="utf-8") as file:
json.dump(inventory, file, indent=4)
file.write("\n")
with open("server_summary.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(["name", "ip", "cpu", "ram_gb", "region", "status"])
for server in servers:
writer.writerow([
server["name"],
server["ip"],
server["cpu"],
server["ram_gb"],
server["region"],
server["status"],
])
print("Updated api-01 to online.")
print(f"Wrote {len(servers)} servers to server_summary.csv.")
How is the full script organized?
- The opening section imports the modules and handles a missing inventory file.
- The successful path updates the selected server and saves the JSON inventory.
- The final section generates the CSV report and prints both completion messages.
That completes the data pipeline. Your JSON inventory now drives an up-to-date CSV report every time the manager runs.
Secret mission
Generate a Region Report
Turn the same JSON inventory into a focused report for one region. You will generate `eu_west_summary.csv` with only the server rows that match `eu-west`.
Clean Up Your Resources
Clean Up Your Resources
Your inventory manager runs entirely on your computer at no ongoing cost. Choose whether to keep the files, close your tools for now, or delete the files permanently.
Resources you used:
- Python source files: tuples_sets.py, read_json.py, write_json.py, csv_basics.py, and inventory_manager.py.
- JSON source of truth: servers.json.
- CSV source copy: servers.csv.
- Full generated CSV report: server_summary.csv.
- Region-filtered CSV report: eu_west_summary.csv.
Keep everything running
No action is needed. Choose this if you want to rerun the inventory manager or experiment with more reports.
- Leave all nine files in their current folder.
- Keep the generated reports as examples of full and region-filtered inventory data.
Pause - I'll come back to this later
Close your editing tools while keeping every project file available for later.
- Close Xed.
- Close Xfce Terminal.
All nine files remain available at no cost. No background service continues running.
Delete - I don't want to use this again
Remove all project files and start fresh. Deleting them is permanent, so the command below targets only this project's named files.
- Return to the Xfce Terminal session where you ran inventory_manager.py.
- Delete the local project files by running this command:
rm -f tuples_sets.py read_json.py write_json.py csv_basics.py inventory_manager.py servers.json servers.csv server_summary.csv eu_west_summary.csv
What Does This Command Remove?
The command removes the nine named files from the folder shown by your terminal prompt. Other files stay in place.
- Confirm the project files are gone by running this command:
ls
What Should You See?
That's the cleanup done. None of the nine project filenames should appear in the output.
🙋♀️ Still See the Project Files?
- Check that your terminal prompt is in the folder where you ran inventory_manager.py.
- Rerun the deletion command above.
Help me remove the local inventory project files safely.
Nice Work!
Nice Work!
Project complete! Your local Python inventory manager now turns one JSON inventory into accurate CSV reports.
You've learned how to:
- Model fixed server records with tuples. Remove duplicate IP addresses with sets. Sort set output for predictable results.
- Load nested JSON into Python dictionaries. Update server statuses. Save readable JSON with four-space indentation.
- Handle a missing inventory file without a traceback. Generate server_summary.csv directly from servers.json. Keep one single source of truth.
- Secret Mission: filter the inventory for eu-west. Generate eu_west_summary.csv with only web-01 and api-01.
Ready to quiz yourself?