AI Event Manager with QR Tickets
Build a CLI app that uses Gemini AI to parse events and generate QR codes.
Introduction
30 Second Summary
Think about how often you get event details as a casual message. "Pizza night Friday at 6 at Mario's for 12 people." Turning that into something organized with a date, time, location, and headcount takes more effort than it should.
In this project, you will build a command-line tool that uses AI to parse natural language event descriptions into structured data, saves them to a database, and generates a QR code ticket image for each event.
What You'll Build
A Python command-line application that integrates Google's Gemini AI, a SQLite database, and QR code generation into a single event management tool.
By the end of this project, you'll have:
- An AI-powered parser that converts natural language like "Team pizza party Friday at 6pm" into structured event fields (title, date, time, location, capacity).
- A SQLite database that stores event records with full create, list, and lookup operations.
- Auto-generated QR code images saved as PNG files for every event you create.
- Secret Mission: Add attendee registration and QR-based check-in tracking.
Are there any prerequisites?
None. This guide walks you through installing Python, getting a free Gemini API key, and setting up everything from scratch. All you need is a computer with VS Code and a Google account.
Before We Start
Before we dive in, let's lock in what you're about to build: a pipeline where an AI parser extracts event details from plain English, a SQLite database stores them, and a QR code generator turns each event into a scannable ticket image.
Set Up Your Development Environment
Your event manager will combine three technologies: AI for parsing natural language, a database for storing events, and a QR code library for generating ticket images.
To bring these together, you need Python (the programming language), two Python packages, and an API key for Google's Gemini AI. In this step, you will install everything and confirm it works.
In this step, get ready to:
- Install Python 3.14.
- Install Visual Studio Code (your code editor).
- Create your project folder, virtual environment, and install packages.
- Get a free Gemini API key and set it as an environment variable.
Install Python
Python is the programming language that runs your entire event manager. You need version 3.14 specifically because the packages you will install later require Python 3.10 or higher.
macOS
- Open your browser and go to https://www.python.org/downloads/.
- Click the large Download Python 3.14.6 button to download the macOS installer.
- Open the downloaded .pkg file.
- Follow the installer prompts, clicking Continue and then Install.
- Click Close when the installer finishes.
Windows
- Open your browser and go to https://www.python.org/downloads/.
- Click the large Download Python 3.14.6 button to download the 64-bit installer.
- Run the downloaded installer file.
- On the first screen of the installer, check the box labelled Add python.exe to PATH at the bottom.
- Click Install Now and wait for the installation to complete.
- Click Close when the installer finishes.
Why check Add python.exe to PATH?
PATH is a list of directories your terminal searches when you type a command. Checking this box means you can type python in any terminal window and Windows will find it automatically.
Linux
Most Linux distributions include Python, but you may need to install 3.14 specifically.
- Open your terminal.
- Update your package list and install Python 3.14 by running:
sudo apt update
sudo apt install python3.14 python3.14-venv
If your distribution does not have Python 3.14 in its default repositories, download the installer from https://www.python.org/downloads/.
Now verify the installation is working.
- Open VS Code. If you don't have VS Code see next step below.
- Open a new terminal by clicking Terminal in the top menu bar, then clicking New Terminal.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
Linux
python3 --version
✔️ I see Python 3.14
You should see output like Python 3.14.6 (the last number may differ slightly). Python is installed and ready to go.
ⓧ I see an older version
If you see a version like Python 3.9.x or Python 3.12.x, an older installation is taking priority on your PATH.
- Download and run the Python 3.14 installer from https://www.python.org/downloads/.
- Check Add python.exe to PATH on the first screen.
- After installing, close and reopen VS Code so it picks up the updated PATH.
- Run python --version again in a fresh terminal.
If the old version persists, press the Windows key, search for Edit environment variables for your account, and move the Python 3.14 entry above any older Python paths in the PATH variable.
ⓧ Command not found
If the terminal says the command is not recognized, Python was not added to your PATH.
- Go to https://www.python.org/downloads/ and download the Python 3.14 installer.
- Run the installer.
- On the first screen, check Add python.exe to PATH at the bottom.
- Click Install Now and wait for completion.
- Close and reopen VS Code, then open a new terminal.
- Run python --version again to confirm it shows Python 3.14.
Install Visual Studio Code
You need a code editor to write and run your Python files. Visual Studio Code (VS Code) is a free editor that works on macOS, Windows, and Linux.
In this project, you will use VS Code to create Python files, edit your code, and run terminal commands all in one window.
What is Visual Studio Code?
VS Code is a free, open-source code editor made by Microsoft. It has a built-in terminal, file explorer, and extensions for almost every programming language.
Think of it as a workspace where you write code on one side and run commands on the other, without switching between apps.
- Open your browser and go to https://code.visualstudio.com/.
macOS
- Click Download for macOS.
- Open the downloaded .dmg file.
- Drag Visual Studio Code.app into the Applications folder.
- Open VS Code from your Applications folder by double-clicking the icon.
Windows
- Click Download for Windows.
- Run the downloaded .exe installer file.
- Accept the license agreement and click Next.
- On the Select Additional Tasks screen, check Add to PATH (this is checked by default).
- Click Install and wait for the installation to finish.
- Click Finish to launch VS Code.
Linux
- Click the .deb download link (for Ubuntu/Debian) or .rpm (for Fedora/SUSE).
- Open the downloaded file. Your package manager will handle the installation.
- Once installed, open VS Code from your application menu.
✔️ I see VS Code open
You should see a Welcome tab inside the VS Code window. VS Code is installed and ready to use.
ⓧ I'm having trouble installing
If the download does not start, try a different browser or go directly to https://code.visualstudio.com/Download for all platform options.
On macOS, if you see a security warning, open System Settings > Privacy & Security and click Open Anyway.
Help me install Visual Studio Code on my computer.
Create your project and install packages
Every Python project needs its own folder and a virtual environment. A virtual environment keeps your project's packages separate from other Python projects on your computer.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop.
- Click New Folder and name it ai-event-manager.
- Select the ai-event-manager folder and click Select Folder.
VS Code now has your project folder open.
- Open a new terminal by clicking Terminal in the top menu bar, then New Terminal.
- Create a virtual environment by running this command:
macOS
python3 -m venv venv
Windows
python -m venv venv
Linux
python3 -m venv venv
- Activate the virtual environment by running:
macOS
source venv/bin/activate
Windows
venv\Scripts\activate
Linux
source venv/bin/activate
You should see (venv) appear at the beginning of your terminal prompt. This confirms you are working inside the virtual environment.
What is a virtual environment?
A virtual environment is an isolated space for your project's packages. Without it, installing a package for one project could break another project that needs a different version of the same package.
The (venv) prefix in your terminal tells you the environment is active and any packages you install will go into this project only.
- Install the two packages your project needs by running:
pip install google-genai==2.11.0 "qrcode[pil]==8.2"
What are these packages?
google-genai is Google's official SDK for connecting to the Gemini AI model. Your app will use it to send natural language event descriptions and get structured data back.
qrcode[pil] generates QR code images. The [pil] part installs Pillow (an image library) so the QR codes can be saved as PNG files.
- Confirm the packages installed correctly by running:
pip list
You should see google-genai and qrcode (along with their dependencies) listed in the output.
Packages not showing up?
Make sure your terminal shows (venv) at the start of the prompt. If it does not, run the activation command again (source venv/bin/activate on macOS/Linux or venv\Scripts\activate on Windows), then re-run the install command.
If you see a permissions error on Windows, make sure you are not running the terminal as an administrator. Virtual environments work best from a regular user terminal.
Help me troubleshoot my pip install failing in a virtual environment.
Get your free Gemini API key
The Gemini API is how your app communicates with Google's AI model. You need an API key to authenticate your requests.
What is Gemini?
Gemini is Google's AI model family. Your project uses gemini-3.5-flash, a fast model that is free to use for input and output tokens. For a learning project with a handful of API calls, you will stay well within the free tier.
- Open your browser and go to https://aistudio.google.com/apikey.
- Sign in with your Google account.
- Click Create API Key.
- Copy the key that appears. Keep this tab open for now.
Keep your API key private
Treat your API key like a password. Never paste it directly into your code files or share it publicly. You will store it in an environment variable instead, which keeps it out of your source code.
- Switch back to your VS Code terminal (make sure it still shows (venv)).
- Set the API key as an environment variable by running this command, replacing your-api-key-here with the key you just copied:
macOS
export GEMINI_API_KEY=your-api-key-here
Windows
set GEMINI_API_KEY=your-api-key-here
Linux
export GEMINI_API_KEY=your-api-key-here
What is an environment variable?
An environment variable is a value stored in your terminal session that programs can read. Your Python code will grab the API key from this variable at runtime instead of having it hard-coded in the file.
This command lasts for the current terminal session only. If you close and reopen the terminal, you will need to run the export (macOS/Linux) or set (Windows) command again before running your event manager.
🙋♀️ API key not working later?
If you see an error about a missing API key when you run your script in a later step, it usually means you opened a new terminal and forgot to re-set the variable. Run the export (macOS/Linux) or set (Windows) command again with your key.
Also make sure there are no spaces around the = sign in the command.
Help me fix my Gemini API key environment variable.
Your development environment is fully set up. Next up, you will create the database that stores your events.
Build the Database Layer
Your development environment is ready with Python, your packages, and your API key all in place.
Now you need somewhere to store event data permanently. In this step, you will create a SQLite database that holds all your event records in a structured table.
In this step, get ready to:
- Create the database schema with a function that builds your events table.
- Test the database by inserting and querying a sample event.
Create the database schema
Every event your app manages needs a permanent home. SQLite is a database engine built into Python, so you do not need to install or configure anything extra.
- In the VS Code Explorer sidebar, click the New File... button.
- Type event_manager.py and press Enter.
Why SQLite?
SQLite stores your entire database in a single file on disk. There is no server to run, no configuration to manage, and Python includes full support out of the box.
This makes it perfect for learning database concepts without any infrastructure overhead.
- Add the following imports at the top of event_manager.py:
# --- Top of event_manager.py ---
import sqlite3
import json
import os
from datetime import datetime
from google import genai
import qrcode
What do these imports do?
- sqlite3 lets you create and interact with your SQLite database.
- json handles converting between JSON strings and Python dictionaries.
- os provides access to environment variables and file system operations.
- datetime gives you timestamps for when events are created.
- genai is the Google Gemini AI client you will use in the next step.
- qrcode generates QR code images, which you will use in a later step.
- Save event_manager.py.
You will not see any output yet. The next chunk defines the function that uses these imports.
Seeing a red underline on an import?
If VS Code highlights from google import genai or import qrcode with an error, your virtual environment may not be selected as the Python interpreter.
- Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows/Linux) to open the Command Palette.
- Type Python: Select Interpreter and select the interpreter from your venv folder.
Help me fix import errors in my event_manager.py file.
- Below the imports, add the database initialization function:
# --- Paste below the imports ---
def init_database():
"""Create the database and events table."""
conn = sqlite3.connect("events.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
date TEXT,
time TEXT,
location TEXT,
max_attendees INTEGER,
qr_code_path TEXT,
created_at TEXT NOT NULL
)
""")
conn.commit()
return conn
What does this function do?
- sqlite3.connect("events.db") creates a new database file called events.db in your project folder, or opens it if it already exists.
- CREATE TABLE IF NOT EXISTS ensures the table is only created once. Running the script multiple times will not cause an error.
- The table stores each event with an auto-incrementing id, a required title, optional fields for date, time, location, and max_attendees, plus a qr_code_path for the QR image and a required created_at timestamp.
- conn.commit() saves the table creation to disk, and the function returns the open connection so other code can use it.
- Save event_manager.py.
The function is defined but not called yet. You will call it in the next substep to verify it works.
Seeing indentation errors?
Python is strict about indentation. Make sure every line inside the function is indented with exactly 4 spaces. The SQL string inside the triple quotes should also be consistently indented.
Help me fix indentation in my init_database function.
Test the database
Before building more features, you should confirm the database and table are created correctly. You will add a temporary test at the bottom of the file that inserts a sample event and reads it back.
- At the bottom of event_manager.py, below the init_database function, add this temporary test code:
# --- Paste at the very bottom of the file (temporary test) ---
conn = init_database()
cursor = conn.cursor()
cursor.execute(
"INSERT INTO events (title, date, time, location, max_attendees, created_at) VALUES (?, ?, ?, ?, ?, ?)",
("Test Event", "2026-07-15", "18:00", "Test Location", 20, "2026-07-09T10:00:00")
)
conn.commit()
cursor.execute("SELECT * FROM events")
print(cursor.fetchall())
conn.close()
What does this test code do?
- It calls init_database() to create the database file and table.
- It inserts one test row with sample data using parameterized queries (the ? placeholders prevent SQL injection).
- It queries all rows back and prints them so you can confirm the data was stored correctly.
- Save event_manager.py.
- Run the script in your terminal by running this command:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
You should see output like this, showing a tuple with your test event data:
[(1, 'Test Event', '2026-07-15', '18:00', 'Test Location', 20, None, '2026-07-09T10:00:00')]
- Confirm that an events.db file now appears in your ai-event-manager folder in the Explorer sidebar.
Seeing a ModuleNotFoundError?
If you see ModuleNotFoundError: No module named 'google', your virtual environment is likely not activated. Run source venv/bin/activate (macOS/Linux) or venv\Scripts\activate (Windows) in your terminal and try again.
If you see a SyntaxError, double check your parentheses and commas in the INSERT statement. Each ? placeholder needs a matching value in the tuple.
Help me debug my event_manager.py script.
- Now that you have confirmed the database works, delete the temporary test code (everything below the init_database function).
- Save event_manager.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved event_manager.py after removing the test code.
ⓧ I'd like to double check the full code
Your event_manager.py should look exactly like this:
import sqlite3
import json
import os
from datetime import datetime
from google import genai
import qrcode
def init_database():
"""Create the database and events table."""
conn = sqlite3.connect("events.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
date TEXT,
time TEXT,
location TEXT,
max_attendees INTEGER,
qr_code_path TEXT,
created_at TEXT NOT NULL
)
""")
conn.commit()
return conn
Your database layer is in place. Next up, you will connect the Gemini AI to parse natural language event descriptions into structured data that fits neatly into this table.
Connect the AI Event Parser
Your database is ready to store events, but right now you would have to manually type each field (title, date, time, location, capacity) one by one.
In this step, you will build a function that sends natural language descriptions to Gemini AI and gets back structured JSON data. This means you can type something like "Team pizza party Friday at 6pm" and the AI will figure out which words map to which fields.
In this step, get ready to:
- Build the AI parsing function that converts natural language into structured data.
- Test the AI parser with a sample event description.
Build the AI parsing function
The Gemini API accepts a text prompt and returns a text response. Your function will craft a prompt that instructs the AI to return only valid JSON, then parse that JSON into a Python dictionary.
- In event_manager.py, find the closing return conn line at the end of init_database().
- Add the following function below it, leaving two blank lines between functions:
# --- Paste below init_database() ---
def parse_event_with_ai(client, user_input):
"""Use Gemini AI to parse natural language into structured event data."""
prompt = (
"Parse the following event description into structured JSON. "
"Return ONLY valid JSON with these fields:\n"
"- title (string): the name/title of the event\n"
"- date (string): the date in YYYY-MM-DD format, or \"TBD\" if not specified\n"
"- time (string): the time in HH:MM format (24-hour), or \"TBD\" if not specified\n"
"- location (string): the location/venue, or \"TBD\" if not specified\n"
"- max_attendees (integer): maximum number of attendees, or 0 if not specified\n\n"
f"Event description: \"{user_input}\""
)
# Send the prompt to Gemini and get the response
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt
)
# Clean up the response text (AI sometimes wraps JSON in code fences)
text = response.text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1]
if text.endswith("```"):
text = text.rsplit("```", 1)[0]
text = text.strip()
# Parse the JSON string into a Python dictionary
return json.loads(text)
What does this code do?
The prompt variable builds a detailed instruction string telling Gemini exactly what JSON fields to return and what format to use for each one. The "Return ONLY valid JSON" instruction keeps the AI from adding extra explanation around the data.
The client.models.generate_content() call sends the prompt to the gemini-3.5-flash model and returns a response object. You access the text via response.text.
The cleanup section handles a common AI behavior where the model wraps its JSON output in markdown code fences (triple backticks). Stripping these ensures json.loads() can parse the response cleanly.
- Save event_manager.py.
Why prompt engineering matters here
The prompt explicitly lists the field names and their expected formats (YYYY-MM-DD, 24-hour time, "TBD" defaults). Without this precision, the AI might return dates like "next Friday" or times like "noon" instead of machine-readable values you can store consistently in your database.
Test the AI parser
You need to verify that the AI actually returns valid, parseable JSON before building the rest of the app on top of it.
- At the very bottom of event_manager.py, add temporary test code by pasting the following:
# --- Paste at the very bottom of the file (temporary test) ---
api_key = os.environ.get("GEMINI_API_KEY")
client = genai.Client(api_key=api_key)
result = parse_event_with_ai(client, "Team lunch next Friday at noon at Pizza Palace for 8 people")
print(result)
What does this test do?
This creates a Gemini client using your GEMINI_API_KEY environment variable, then calls your parsing function with a sample event description. If everything works, it prints a Python dictionary with the parsed fields.
- Save event_manager.py.
- Run the script by entering this command in your terminal:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
You should see output like a dictionary containing fields such as title, date, time, location, and max_attendees populated with values parsed from your input sentence.
Seeing an error?
- If you see an error about the API key, make sure you set the environment variable in the same terminal session. Run export GEMINI_API_KEY=your-api-key-here (macOS/Linux) or set GEMINI_API_KEY=your-api-key-here (Windows) again if needed.
- If you see a JSON parsing error, the AI may have returned unexpected formatting. Run the script again. AI responses can vary slightly between calls.
- If you see an import error for genai, confirm your virtual environment is activated. You should see (venv) at the start of your terminal prompt.
Still stuck? Ask for help.
- Once you have confirmed the test prints a valid dictionary, delete the temporary test code (the last 4 lines you added) from event_manager.py.
- Save the file.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file with the test code removed.
ⓧ I'd like to double check the full code
Here is the complete event_manager.py at this point. Compare yours against it section by section.
import sqlite3
import json
import os
from datetime import datetime
from google import genai
import qrcode
def init_database():
"""Create the database and events table."""
conn = sqlite3.connect("events.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
date TEXT,
time TEXT,
location TEXT,
max_attendees INTEGER,
qr_code_path TEXT,
created_at TEXT NOT NULL
)
""")
conn.commit()
return conn
def parse_event_with_ai(client, user_input):
"""Use Gemini AI to parse natural language into structured event data."""
prompt = (
"Parse the following event description into structured JSON. "
"Return ONLY valid JSON with these fields:\n"
"- title (string): the name/title of the event\n"
"- date (string): the date in YYYY-MM-DD format, or \"TBD\" if not specified\n"
"- time (string): the time in HH:MM format (24-hour), or \"TBD\" if not specified\n"
"- location (string): the location/venue, or \"TBD\" if not specified\n"
"- max_attendees (integer): maximum number of attendees, or 0 if not specified\n\n"
f"Event description: \"{user_input}\""
)
# Send the prompt to Gemini and get the response
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt
)
# Clean up the response text (AI sometimes wraps JSON in code fences)
text = response.text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1]
if text.endswith("```"):
text = text.rsplit("```", 1)[0]
text = text.strip()
# Parse the JSON string into a Python dictionary
return json.loads(text)
Your AI parser is working. Next up, you will generate QR code images for each event and wire everything together into a complete workflow.
Generate QR Codes for Events
Your AI parser can turn casual event descriptions into structured data. Now it's time to give each event a tangible output that people can actually scan with their phones.
In this step, you'll build a QR code generator that encodes event information into PNG images. You'll also wire together the full event-creation workflow that chains the AI parser, database, and QR generator into one smooth interaction.
In this step, get ready to:
- Build a function that generates QR code PNG images for events.
- Create the full add-event workflow combining AI parsing, database storage, and QR generation.
Build the QR code generator function
Each event needs a unique QR code that encodes its ID and title. Scanning it later could pull up the event details or serve as a digital ticket.
- In event_manager.py, scroll to the bottom of the file (below the parse_event_with_ai function).
- Add the QR code generator function by copying the following code:
# --- Paste below parse_event_with_ai() ---
def generate_qr_code(event_id, event_title):
"""Generate a QR code PNG for an event."""
# Create the qr_codes folder if it doesn't exist
os.makedirs("qr_codes", exist_ok=True)
# Encode the event ID and title into the QR data
qr_data = f"EVENT-{event_id}|{event_title}"
# Configure the QR code settings
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(qr_data)
qr.make(fit=True)
# Generate and save the image
img = qr.make_image(fill_color="black", back_color="white")
file_path = f"qr_codes/event_{event_id}.png"
img.save(file_path)
return file_path
What does this code do?
This function creates a scannable QR code image for each event and saves it as a PNG file.
- os.makedirs("qr_codes", exist_ok=True) creates the output folder the first time. The exist_ok=True flag prevents an error if the folder already exists.
- qr_data encodes the event ID and title into a single string. Anyone scanning the QR code gets this text.
- qrcode.QRCode() creates the QR code object with settings for size, error tolerance, and padding.
- make_image() renders the final image, and .save() writes it to disk as a PNG.
💡 What is error correction in QR codes?
QR codes can still be scanned even when part of the image is damaged or obscured. The ERROR_CORRECT_L setting means about 7% of the code can be lost while still remaining readable. Higher levels (M, Q, H) tolerate more damage but make the QR code denser.
- Save event_manager.py.
Before building the full workflow, test that the QR generator works on its own.
- Add this temporary test at the very bottom of event_manager.py to call generate_qr_code directly:
# --- Paste at the very bottom of the file (temporary test) ---
result = generate_qr_code(99, "Test QR Code")
print(f"QR code saved to: {result}")
- Save event_manager.py and run the script:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
You should see QR code saved to: qr_codes/event_99.png printed in the terminal.
- Confirm the file exists by running:
macOS
ls qr_codes
Windows
dir qr_codes
Linux
ls qr_codes
You should see event_99.png listed. Double-click it in the VS Code Explorer sidebar to preview the QR code image.
QR code not generating?
If you see ModuleNotFoundError: No module named 'PIL', Pillow did not install correctly. Re-run pip install "qrcode[pil]==8.2" with your virtual environment activated, then try again.
If you see ModuleNotFoundError: No module named 'qrcode', your virtual environment is not active. Run the activation command (source venv/bin/activate on macOS/Linux or venv\Scripts\activate on Windows) and try again.
- Once you see the QR code image, delete the two temporary test lines from the bottom of the file (the result = generate_qr_code(...) and print(...) lines).
- Save event_manager.py.
Integrate QR generation with event creation
The add-event workflow is the centrepiece of the application. It ties together the AI parser, database, and QR generator into a single user interaction.
- In event_manager.py, add the following function below generate_qr_code:
# --- Paste below generate_qr_code() ---
def add_event(conn, client):
"""Add a new event using natural language input."""
print("\nDescribe your event in natural language:")
print("Example: 'Team lunch next Friday at noon at Pizza Palace for 8 people'")
user_input = input("\n> ")
# Send the description to the AI for parsing
print("\nParsing with AI...")
try:
event_data = parse_event_with_ai(client, user_input)
except (json.JSONDecodeError, Exception) as e:
print(f"Error parsing event: {e}")
return
# Display the parsed result for confirmation
print(f"\nParsed event:")
print(f" Title: {event_data.get('title', 'Unknown')}")
print(f" Date: {event_data.get('date', 'TBD')}")
print(f" Time: {event_data.get('time', 'TBD')}")
print(f" Location: {event_data.get('location', 'TBD')}")
print(f" Max Attendees: {event_data.get('max_attendees', 0)}")
confirm = input("\nSave this event? (y/n): ")
if confirm.lower() != "y":
print("Event cancelled.")
return
# Insert the event into the database
cursor = conn.cursor()
cursor.execute(
"INSERT INTO events (title, date, time, location, max_attendees, created_at) VALUES (?, ?, ?, ?, ?, ?)",
(event_data.get("title", "Untitled Event"), event_data.get("date", "TBD"),
event_data.get("time", "TBD"), event_data.get("location", "TBD"),
event_data.get("max_attendees", 0), datetime.now().isoformat()),
)
conn.commit()
# Generate QR code and update the database record
event_id = cursor.lastrowid
qr_path = generate_qr_code(event_id, event_data.get("title", "Untitled"))
cursor.execute("UPDATE events SET qr_code_path = ? WHERE id = ?", (qr_path, event_id))
conn.commit()
print(f"\nEvent saved with ID: {event_id}")
print(f"QR code saved to: {qr_path}")
What does this code do?
This function handles the complete event-creation flow in five stages:
- Prompts the user to describe an event in everyday language.
- Sends the description to Gemini via parse_event_with_ai and catches any JSON errors.
- Displays the structured result and asks for confirmation before saving.
- Inserts the confirmed event into the SQLite database and retrieves the new row's ID.
- Calls generate_qr_code with the new ID, then updates the database record with the saved file path.
- Save event_manager.py.
- Now add temporary test code at the very bottom of the file so you can try the workflow:
# --- Paste at the very bottom of the file (temporary test) ---
if __name__ == "__main__":
api_key = os.environ.get("GEMINI_API_KEY")
client = genai.Client(api_key=api_key)
conn = init_database()
add_event(conn, client)
conn.close()
- Save the file, then run the script in your terminal:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
- When prompted, type a natural language event description like Team pizza party Friday at 6pm at Mario's for 12 people and press Enter.
- Review the parsed output and type y to confirm.
- Confirm the output shows "Event saved with ID" and "QR code saved to" messages.
- Open the qr_codes folder in the VS Code Explorer sidebar. You should see a PNG file (e.g. event_1.png).
- Double-click the PNG to preview the QR code image.
Not seeing a QR code file?
- Make sure you typed y when asked to confirm. Typing anything else cancels the save.
- Check that your GEMINI_API_KEY environment variable is still set. If you opened a new terminal, you may need to run export GEMINI_API_KEY=your-api-key-here (macOS/Linux) or set GEMINI_API_KEY=your-api-key-here (Windows) again.
- If you see a ModuleNotFoundError, make sure your virtual environment is activated (you should see (venv) at the start of your terminal prompt).
Still stuck? Help me debug why my QR code file is not being generated.
- Once you have confirmed the QR code was generated, delete the temporary test code block (the if __name__ == "__main__": section) from the bottom of the file.
- Save event_manager.py.
✔️ Awesome, I've got everything!
Great. Make sure you deleted the temporary test code and saved the file.
ⓧ I'd like to double check the full code
Here is the complete event_manager.py at this point. Compare yours against it section by section.
import sqlite3
import json
import os
from datetime import datetime
from google import genai
import qrcode
def init_database():
"""Create the database and events table."""
conn = sqlite3.connect("events.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
date TEXT,
time TEXT,
location TEXT,
max_attendees INTEGER,
qr_code_path TEXT,
created_at TEXT NOT NULL
)
""")
conn.commit()
return conn
def parse_event_with_ai(client, user_input):
"""Use Gemini AI to parse natural language into structured event data."""
prompt = (
"Parse the following event description into structured JSON. "
"Return ONLY valid JSON with these fields:\n"
"- title (string): the name/title of the event\n"
"- date (string): the date in YYYY-MM-DD format, or \"TBD\" if not specified\n"
"- time (string): the time in HH:MM format (24-hour), or \"TBD\" if not specified\n"
"- location (string): the location/venue, or \"TBD\" if not specified\n"
"- max_attendees (integer): maximum number of attendees, or 0 if not specified\n\n"
f"Event description: \"{user_input}\""
)
# Send the prompt to Gemini and get the response
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt
)
# Clean up the response text (AI sometimes wraps JSON in code fences)
text = response.text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1]
if text.endswith("```"):
text = text.rsplit("```", 1)[0]
text = text.strip()
# Parse the JSON string into a Python dictionary
return json.loads(text)
def generate_qr_code(event_id, event_title):
"""Generate a QR code PNG for an event."""
# Create the qr_codes folder if it doesn't exist
os.makedirs("qr_codes", exist_ok=True)
# Encode the event ID and title into the QR data
qr_data = f"EVENT-{event_id}|{event_title}"
# Configure the QR code settings
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(qr_data)
qr.make(fit=True)
# Generate and save the image
img = qr.make_image(fill_color="black", back_color="white")
file_path = f"qr_codes/event_{event_id}.png"
img.save(file_path)
return file_path
def add_event(conn, client):
"""Add a new event using natural language input."""
print("\nDescribe your event in natural language:")
print("Example: 'Team lunch next Friday at noon at Pizza Palace for 8 people'")
user_input = input("\n> ")
# Send the description to the AI for parsing
print("\nParsing with AI...")
try:
event_data = parse_event_with_ai(client, user_input)
except (json.JSONDecodeError, Exception) as e:
print(f"Error parsing event: {e}")
return
# Display the parsed result for confirmation
print(f"\nParsed event:")
print(f" Title: {event_data.get('title', 'Unknown')}")
print(f" Date: {event_data.get('date', 'TBD')}")
print(f" Time: {event_data.get('time', 'TBD')}")
print(f" Location: {event_data.get('location', 'TBD')}")
print(f" Max Attendees: {event_data.get('max_attendees', 0)}")
confirm = input("\nSave this event? (y/n): ")
if confirm.lower() != "y":
print("Event cancelled.")
return
# --- add_event() continued ---
# Insert the event into the database
cursor = conn.cursor()
cursor.execute(
"INSERT INTO events (title, date, time, location, max_attendees, created_at) VALUES (?, ?, ?, ?, ?, ?)",
(event_data.get("title", "Untitled Event"), event_data.get("date", "TBD"),
event_data.get("time", "TBD"), event_data.get("location", "TBD"),
event_data.get("max_attendees", 0), datetime.now().isoformat()),
)
conn.commit()
# Generate QR code and update the database record
event_id = cursor.lastrowid
qr_path = generate_qr_code(event_id, event_data.get("title", "Untitled"))
cursor.execute("UPDATE events SET qr_code_path = ? WHERE id = ?", (qr_path, event_id))
conn.commit()
print(f"\nEvent saved with ID: {event_id}")
print(f"QR code saved to: {qr_path}")
Your event manager can now parse natural language, save structured data, and produce a real QR code image. Next up, you'll build the full menu system so users can list events, look them up, and run everything from one command.
Build the Complete App
Your event manager can already parse natural language with AI, store events in a database, and generate QR codes. Each piece works, but they only run when you manually call functions in test code.
In this step, you will tie everything together with a menu-driven interface. You will also add functions to browse and look up saved events, turning your collection of functions into a polished command-line application.
In this step, get ready to:
- Add listing and lookup functions to browse saved events.
- Build a main menu loop that ties all features together.
- Test the complete application end-to-end.
Add event listing and lookup functions
Your app can create events, but users also need a way to see what is already in the database. You will add two retrieval functions: one that lists all events at a glance, and one that shows full details for a single event.
- If you have any test code at the bottom of event_manager.py from earlier steps, delete it now.
- Add the list_events function below your add_event function by copying this code:
# --- Paste below add_event() ---
def list_events(conn):
"""Display all events in the database."""
cursor = conn.cursor()
cursor.execute(
"SELECT id, title, date, time, location, max_attendees FROM events"
)
events = cursor.fetchall()
if not events:
print("\nNo events found.")
return
print("\n--- All Events ---")
for event in events:
print(f"\n ID: {event[0]}")
print(f" Title: {event[1]}")
print(f" Date: {event[2]} at {event[3]}")
print(f" Location: {event[4]}")
print(f" Max Attendees: {event[5]}")
print()
What does this code do?
- cursor.execute("SELECT ...") queries the database for every event, pulling only the columns needed for a summary view.
- fetchall() returns all matching rows as a list of tuples.
- The early return when events is empty prevents printing a header with no content beneath it.
You will not see a change yet. The next function (lookup_event) completes the retrieval pair, and then the main menu will make both runnable.
- Add the lookup_event function directly below list_events by copying this code:
# --- Paste below list_events() ---
def lookup_event(conn):
"""Look up a specific event by ID."""
print("\nEnter event ID to look up:")
try:
event_id = int(input("> "))
except ValueError:
print("Invalid ID.")
return
cursor = conn.cursor()
cursor.execute("SELECT * FROM events WHERE id = ?", (event_id,))
event = cursor.fetchone()
if not event:
print(f"No event found with ID {event_id}.")
return
print("\n--- Event Details ---")
print(f" ID: {event[0]}")
print(f" Title: {event[1]}")
print(f" Date: {event[2]}")
print(f" Time: {event[3]}")
print(f" Location: {event[4]}")
print(f" Max Attendees: {event[5]}")
print(f" QR Code: {event[6]}")
print(f" Created: {event[7]}")
What does this code do?
- int(input(">")) asks the user to type an event ID. The try/except ValueError catches non-numeric input gracefully instead of crashing.
- The ? placeholder in the SQL query prevents SQL injection by letting SQLite safely substitute the value.
- SELECT * retrieves all columns for the single event, including qr_code_path and created_at for a detailed view.
You will not see a change yet. The next chunk builds the main menu that calls both of these functions.
Build the main menu loop
A command-line application needs a single entry point that initializes everything and presents options to the user. The main() function reads your API key, sets up the database connection, and runs an infinite loop that only breaks when the user chooses to quit.
- Add the main() function and the entry point below lookup_event by copying this code:
# --- Paste below lookup_event() ---
def main():
"""Main application entry point."""
# Read the API key from environment variables
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print("Error: GEMINI_API_KEY environment variable not set.")
print("Get your free API key at: https://aistudio.google.com/apikey")
return
# Create the Gemini client and initialize the database
client = genai.Client(api_key=api_key)
conn = init_database()
print("=" * 40)
print(" AI Event Manager with QR Tickets")
print("=" * 40)
# Main menu loop
while True:
print("\nWhat would you like to do?")
print(" 1. Add new event (describe in natural language)")
print(" 2. List all events")
print(" 3. Look up event by ID")
print(" 4. Quit")
choice = input("\nChoice (1-4): ")
if choice == "1":
add_event(conn, client)
elif choice == "2":
list_events(conn)
elif choice == "3":
lookup_event(conn)
elif choice == "4":
print("\nGoodbye!")
conn.close()
break
else:
print("Invalid choice. Please enter 1-4.")
if __name__ == "__main__":
main()
What does this code do?
- os.environ.get("GEMINI_API_KEY") reads your API key from the environment variable you set in Step 1. If the key is missing, the app prints a helpful error and exits.
- genai.Client(api_key=api_key) creates the Gemini client once, then passes it to add_event each time the user creates an event.
- The while True loop keeps the menu running until the user types 4 to quit. At that point, conn.close() cleanly shuts down the database connection.
- if __name__ == "__main__" ensures main() only runs when you execute the script directly, not when it is imported as a module.
- Save event_manager.py.
- Run the app by entering this command in your terminal:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
- You should see the app banner and a numbered menu with four options.
- Type 4 and press Enter to quit for now.
Seeing an error when you run the script?
- If you see "GEMINI_API_KEY environment variable not set", your terminal session may have lost the variable. Re-run export GEMINI_API_KEY=your-api-key-here (macOS/Linux) or set GEMINI_API_KEY=your-api-key-here (Windows) with your actual key.
- If you see a SyntaxError, check that your indentation is consistent. Every line inside main() should be indented 4 spaces.
- If you see "NameError: name 'list_events' is not defined", make sure you added list_events and lookup_event above main() in the file.
Help me debug my event_manager.py script
✔️ Awesome, I've got everything!
Great. Double check you have saved your file, then continue to the end-to-end test below.
ⓧ I'd like to double check the full code
Here is the complete event_manager.py file. Compare yours against it section by section.
import sqlite3
import json
import os
from datetime import datetime
from google import genai
import qrcode
def init_database():
"""Create the database and events table."""
conn = sqlite3.connect("events.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
date TEXT,
time TEXT,
location TEXT,
max_attendees INTEGER,
qr_code_path TEXT,
created_at TEXT NOT NULL
)
""")
conn.commit()
return conn
def parse_event_with_ai(client, user_input):
"""Use Gemini AI to parse natural language into structured event data."""
prompt = (
"Parse the following event description into structured JSON. "
"Return ONLY valid JSON with these fields:\n"
"- title (string): the name/title of the event\n"
"- date (string): the date in YYYY-MM-DD format, or \"TBD\" if not specified\n"
"- time (string): the time in HH:MM format (24-hour), or \"TBD\" if not specified\n"
"- location (string): the location/venue, or \"TBD\" if not specified\n"
"- max_attendees (integer): maximum number of attendees, or 0 if not specified\n\n"
f"Event description: \"{user_input}\""
)
# Send the prompt to Gemini and get the response
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=prompt
)
# Clean up the response text (AI sometimes wraps JSON in code fences)
text = response.text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1]
if text.endswith("```"):
text = text.rsplit("```", 1)[0]
text = text.strip()
# Parse the JSON string into a Python dictionary
return json.loads(text)
def generate_qr_code(event_id, event_title):
"""Generate a QR code PNG for an event."""
# Create the qr_codes folder if it doesn't exist
os.makedirs("qr_codes", exist_ok=True)
# Encode the event ID and title into the QR data
qr_data = f"EVENT-{event_id}|{event_title}"
# Configure the QR code settings
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(qr_data)
qr.make(fit=True)
# Generate and save the image
img = qr.make_image(fill_color="black", back_color="white")
file_path = f"qr_codes/event_{event_id}.png"
img.save(file_path)
return file_path
def add_event(conn, client):
"""Add a new event using natural language input."""
print("\nDescribe your event in natural language:")
print("Example: 'Team lunch next Friday at noon at Pizza Palace for 8 people'")
user_input = input("\n> ")
# Send the description to the AI for parsing
print("\nParsing with AI...")
try:
event_data = parse_event_with_ai(client, user_input)
except (json.JSONDecodeError, Exception) as e:
print(f"Error parsing event: {e}")
return
# Display the parsed result for confirmation
print(f"\nParsed event:")
print(f" Title: {event_data.get('title', 'Unknown')}")
print(f" Date: {event_data.get('date', 'TBD')}")
print(f" Time: {event_data.get('time', 'TBD')}")
print(f" Location: {event_data.get('location', 'TBD')}")
print(f" Max Attendees: {event_data.get('max_attendees', 0)}")
confirm = input("\nSave this event? (y/n): ")
if confirm.lower() != "y":
print("Event cancelled.")
return
# --- add_event() continued ---
# Insert the event into the database
cursor = conn.cursor()
cursor.execute(
"INSERT INTO events (title, date, time, location, max_attendees, created_at) VALUES (?, ?, ?, ?, ?, ?)",
(event_data.get("title", "Untitled Event"), event_data.get("date", "TBD"),
event_data.get("time", "TBD"), event_data.get("location", "TBD"),
event_data.get("max_attendees", 0), datetime.now().isoformat()),
)
conn.commit()
# Generate QR code and update the database record
event_id = cursor.lastrowid
qr_path = generate_qr_code(event_id, event_data.get("title", "Untitled"))
cursor.execute("UPDATE events SET qr_code_path = ? WHERE id = ?", (qr_path, event_id))
conn.commit()
print(f"\nEvent saved with ID: {event_id}")
print(f"QR code saved to: {qr_path}")
def list_events(conn):
"""Display all events in the database."""
cursor = conn.cursor()
cursor.execute(
"SELECT id, title, date, time, location, max_attendees FROM events"
)
events = cursor.fetchall()
if not events:
print("\nNo events found.")
return
print("\n--- All Events ---")
for event in events:
print(f"\n ID: {event[0]}")
print(f" Title: {event[1]}")
print(f" Date: {event[2]} at {event[3]}")
print(f" Location: {event[4]}")
print(f" Max Attendees: {event[5]}")
print()
def lookup_event(conn):
"""Look up a specific event by ID."""
print("\nEnter event ID to look up:")
try:
event_id = int(input("> "))
except ValueError:
print("Invalid ID.")
return
cursor = conn.cursor()
cursor.execute("SELECT * FROM events WHERE id = ?", (event_id,))
event = cursor.fetchone()
if not event:
print(f"No event found with ID {event_id}.")
return
print("\n--- Event Details ---")
print(f" ID: {event[0]}")
print(f" Title: {event[1]}")
print(f" Date: {event[2]}")
print(f" Time: {event[3]}")
print(f" Location: {event[4]}")
print(f" Max Attendees: {event[5]}")
print(f" QR Code: {event[6]}")
print(f" Created: {event[7]}")
def main():
"""Main application entry point."""
# Read the API key from environment variables
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print("Error: GEMINI_API_KEY environment variable not set.")
print("Get your free API key at: https://aistudio.google.com/apikey")
return
# Create the Gemini client and initialize the database
client = genai.Client(api_key=api_key)
conn = init_database()
print("=" * 40)
print(" AI Event Manager with QR Tickets")
print("=" * 40)
# --- main() continued ---
# Main menu loop
while True:
print("\nWhat would you like to do?")
print(" 1. Add new event (describe in natural language)")
print(" 2. List all events")
print(" 3. Look up event by ID")
print(" 4. Quit")
choice = input("\nChoice (1-4): ")
if choice == "1":
add_event(conn, client)
elif choice == "2":
list_events(conn)
elif choice == "3":
lookup_event(conn)
elif choice == "4":
print("\nGoodbye!")
conn.close()
break
else:
print("Invalid choice. Please enter 1-4.")
if __name__ == "__main__":
main()
Run your end-to-end test
Your app is complete. Time to test every feature together and confirm data persists between runs.
- Run the app by entering this command:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
- Type 1 to add a new event.
- Describe an event in natural language. For example: Team pizza party Friday at 6pm at Mario's for 12 people.
- The AI parses your description and shows the structured fields. Type y to confirm and save.
- Add 1-2 more events with different descriptions. Try varied phrasing like Workshop on Python Saturday morning at the library.
- Type 2 to list all events. You should see every event you added.
- Type 3 and enter an event ID to see its full details, including the QR code file path.
- Type 4 to quit.
How do I know it's working?
- Each event you add should display a parsed summary with title, date, time, location, and max attendees.
- The terminal should confirm "Event saved with ID: X" and "QR code saved to: qr_codes/event_X.png".
- Open the qr_codes/ folder in your file explorer. You should see a PNG file for each event you saved.
- Run the app one more time to confirm persistence:
macOS
python3 event_manager.py
Windows
python event_manager.py
Linux
python3 event_manager.py
- Type 2 to list events. All events from your previous session should still appear.
Why do events persist?
SQLite stores data in the events.db file on disk. Unlike variables in memory, database files survive after the script ends. Every time you restart the app, init_database() reconnects to the same file and all your records are still there.
🙋♀️ Events disappearing between runs?
- Make sure you are running the script from the same folder each time. If you cd elsewhere, a new events.db is created in that different directory.
- Check that you typed y when prompted "Save this event? (y/n)". Typing anything other than y cancels the save.
Help me figure out why my events are not persisting
Your AI Event Manager is fully working. You have built a complete application that combines AI text parsing, a relational database, and QR code generation. Next up: the Secret Mission adds attendee registration and check-in tracking to take this app even further.
Secret mission
Add Attendee Check-In Tracking
Your event manager creates events and generates QR codes. But what about the people attending? Extend your app with attendee registration, capacity enforcement, and check-in tracking to build a complete event management system.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- ai-event-manager project folder (includes event_manager.py, venv/, events.db, and qr_codes/).
- Gemini API key (free tier, no charges).
Keep everything running
No action needed. Choose this if you're still actively building or plan to extend the project further.
- All files are stored locally on your machine. There are no servers, containers, or cloud resources running.
- Your Gemini API key is on the free tier, so it costs nothing even if you leave it active.
- Your virtual environment stays ready for the next time you open the project.
Pause - I'll come back to this later
Shut down any running processes to free up terminal resources. Your files stay on disk ready for next time.
- If the script is still running, press Ctrl+C in the terminal to stop it.
- Deactivate your virtual environment by running:
deactivate
- When you come back, reactivate the environment with venv\Scripts\activate (Windows) or source venv/bin/activate (macOS/Linux).
Delete - I don't want to use this again
Remove all project files and optionally revoke your API key.
- Close the ai-event-manager folder in VS Code (File > Close Folder).
- Delete the entire project folder. On Windows, run this in a new terminal:
rmdir /s /q %USERPROFILE%\Desktop\ai-event-manager
- On macOS or Linux, run:
rm -rf ~/Desktop/ai-event-manager
- This removes event_manager.py, events.db, the qr_codes/ folder, and the venv/ virtual environment all at once.
- To revoke your Gemini API key, go to https://aistudio.google.com/apikey and delete the key you created for this project. You can also keep it for future projects at no cost.
Nice Work!
Nice Work!
Nice work! You just built a complete AI-powered event management tool from scratch using Python.
You've learned how to:
- Integrated the Gemini AI API to parse natural language event descriptions into structured JSON data.
- Created a SQLite database with a proper schema and full CRUD operations to persist events between runs.
- Generated QR code PNG images programmatically for each event using the qrcode library.
- Secret Mission: Added attendee registration with capacity checking and QR-based check-in tracking.
Ready to quiz yourself?