A Python application that fetches entertainment events from The Villages API and outputs formatted event data. This tool replaces the original bash shell script (villages_square_events.sh) with a more maintainable, well-structured Python implementation that offers better error handling, multiple output formats, and comprehensive documentation.
The Villages Event Scraper retrieves today's entertainment events at The Villages town squares by:
- Extracting an authentication token from The Villages JavaScript files
- Establishing an HTTP session with proper cookies
- Making authenticated API requests to fetch event data
- Processing venue names with configurable abbreviations
- Outputting formatted event information in multiple formats
- Python 3.8 or higher
- pip (Python package installer)
Install the package so you can run it from anywhere without python prefix:
# Clone the repository
git clone https://github.com/yourusername/villages-event-scraper.git
cd villages-event-scraper
# Install in editable mode (for development)
pip install -e .
# Or install normally
pip install .After installation, you can run the command from anywhere:
villages-events
villages-events --help
villages-events --format jsonIf you prefer not to install:
# Clone the repository
git clone https://github.com/yourusername/villages-event-scraper.git
cd villages-event-scraper
# Install dependencies only
pip install -r requirements.txt
# Run with python
villages-eventsThe application requires:
requests- HTTP library for API requests and session managementpyyaml- YAML parser for configuration file support
Create a configuration file to set your preferred defaults:
cp config.yaml.example config.yaml
# Edit config.yaml to customize settingsIf installed as a command:
villages-eventsIf running with Python:
villages-eventsFor the rest of this documentation, examples will use villages-events (installed command). If you're running with Python, replace villages-events with villages-events.
The application supports two main options:
Specify which events to fetch using the --date-range option:
villages-events --date-range today # Today's events (default)
villages-events --date-range tomorrow # Tomorrow's events
villages-events --date-range this-week # This week's events
villages-events --date-range next-week # Next week's events
villages-events --date-range this-month # This month's events
villages-events --date-range next-month # Next month's events
villages-events --date-range all # All events (no date filter)Filter events by category using the --category option:
villages-events --category entertainment # Entertainment events (default)
villages-events --category arts-and-crafts # Arts and crafts events
villages-events --category health-and-wellness # Health and wellness events
villages-events --category recreation # Recreation events
villages-events --category social-clubs # Social club events
villages-events --category special-events # Special events
villages-events --category sports # Sports events
villages-events --category all # All categories (no filter)Filter events by location using the --location option:
villages-events --location town-squares # Town squares (default)
villages-events --location Brownwood+Paddock+Square # Brownwood Paddock Square
villages-events --location Spanish+Springs+Town+Square # Spanish Springs Town Square
villages-events --location Lake+Sumter+Landing+Market+Square # Lake Sumter Landing
villages-events --location Sawgrass+Grove # Sawgrass Grove
villages-events --location The+Sharon # The Sharon
villages-events --location sports-recreation # Sports & recreation venues
villages-events --location all # All locations (no filter)See --help for the complete list of 15 location options.
The application supports multiple output formats via the --format option:
Compact format optimized for Meshtastic messaging:
villages-events --format meshtasticOutput example:
Brownwood,John Doe#Spanish Springs,Jane Smith#Sawgrass,The Band#
Format: venue1,title1#venue2,title2# (hash-delimited with trailing #)
Structured JSON array output:
villages-events --format jsonOutput example:
[
{"venue": "Brownwood", "title": "John Doe"},
{"venue": "Spanish Springs", "title": "Jane Smith"},
{"venue": "Sawgrass", "title": "The Band"}
]Comma-separated values with headers:
villages-events --format csvOutput example:
venue,title
Brownwood,John Doe
Spanish Springs,Jane Smith
Sawgrass,The Band
Human-readable format, one event per line:
villages-events --format plainOutput example:
Brownwood: John Doe
Spanish Springs: Jane Smith
Sawgrass: The Band
The application allows you to customize which fields from the API response are included in the output. This gives you control over the information displayed, from basic venue and title to detailed event information including dates, descriptions, addresses, and more.
Specify custom fields using the --fields command-line argument with a comma-separated list:
villages-events --fields location.title,title,start.dateThe following fields can be included in your output using dot notation for nested fields:
| Field Path | Description | Example Value |
|---|---|---|
title |
Event title | "John Doe Band" |
description |
Full event description | "Join us for an evening of..." |
excerpt |
Short event description | "Live music performance" |
category |
Event category | "entertainment" |
subcategories |
List of subcategories | ["live-music", "outdoor"] |
start.date |
Event start date/time | "2025-11-14T22:00:00.000Z" |
end.date |
Event end date/time | "2025-11-15T01:00:00.000Z" |
allDay |
Whether event is all day | false |
cancelled |
Whether event is cancelled | false |
featured |
Whether event is featured | true |
location.title |
Venue name (abbreviated) | "Brownwood Paddock Square" |
location.category |
Venue category | "town-squares" |
location.id |
Venue ID | "brownwood-paddock-square" |
address.streetAddress |
Street address | "1101 Canal Street" |
address.locality |
City/locality | "The Villages" |
address.region |
State/region | "FL" |
address.postalCode |
Postal code | "32162" |
address.country |
Country | "US" |
image |
Event image URL | "https://..." |
url |
Event URL | "https://..." |
otherInfo |
Additional information | "Free admission" |
id |
Event ID | "event-12345" |
Note: The location.title field is the only field that applies venue abbreviation rules based on your venue_mappings configuration.
Example 1: Basic event listing with start time
villages-events --fields location.title,title,start.date --format jsonOutput:
[
{
"location.title": "Brownwood",
"title": "John Doe Band",
"start.date": "2025-11-14T22:00:00.000Z"
}
]Example 2: Detailed event information
villages-events --fields title,location.title,start.date,description,url --format plainOutput:
title: John Doe Band, location.title: Brownwood, start.date: 2025-11-14T22:00:00.000Z, description: Join us for an evening of live music, url: https://...
Example 3: Event with full address
villages-events --fields location.title,title,address.streetAddress,address.locality,address.region --format csvOutput:
location.title,title,address.streetAddress,address.locality,address.region
Brownwood,John Doe Band,1101 Canal Street,The Villages,FL
Example 4: Meshtastic format with custom fields
villages-events --fields title,start.date --format meshtasticOutput:
John Doe Band,2025-11-14T22:00:00.000Z#Jane Smith,2025-11-14T23:00:00.000Z#
Note: In Meshtastic format, only the first two fields are used to maintain the compact field1,field2# format.
Example 5: Event category and featured status
villages-events --fields location.title,title,category,featured --format jsonOutput:
[
{
"location.title": "Spanish Springs",
"title": "The Band",
"category": "entertainment",
"featured": true
}
]You can set default output fields in your config.yaml file to avoid specifying them on every command:
output_fields:
- location.title
- title
- start.date
- categoryThen simply run:
villages-events --format jsonCommand-line --fields argument will override the configuration file setting.
For Meshtastic messaging (compact format):
# Default: venue and title
villages-events --format meshtastic
# With start time
villages-events --fields location.title,start.date --format meshtasticFor event calendars:
villages-events --fields title,location.title,start.date,end.date,description --format jsonFor location-based apps:
villages-events --fields title,location.title,address.streetAddress,address.locality,url --format csvFor event discovery:
villages-events --fields title,excerpt,category,location.title,image,url --format jsonFor simple listings:
villages-events --fields location.title,title --format plainWhen no --fields argument is provided and no output_fields is set in the configuration file, the application defaults to location.title,title to maintain backward compatibility with the original implementation.
You can add a preamble string before the output using the -p or --preamble option. This is useful for adding headers, labels, or formatting:
# Add a simple label (automatically adds newline separator)
villages-events --preamble "Today's Events:"
# Add a header with newline (no extra separator added)
villages-events --preamble "=== Villages Events ===\n" --format json
# Add multiple lines
villages-events -p "Schedule\n--------\n" --format plain
# Combine with other options
villages-events --date-range tomorrow --preamble "Tomorrow:" --format meshtasticNote: If your preamble doesn't end with a newline (\n), a newline separator will be automatically added between the preamble and the output.
The preamble can also be set in the configuration file:
# config.yaml
preamble: "Events:\n"Command-line --preamble argument will override the configuration file setting.
Save output to a file:
villages-events --format json > events.json
villages-events --format csv > events.csv
# With preamble
villages-events --preamble "# Events Report\n" --format csv > events.csvFor debugging or exploring the API response structure, use the --raw flag to output the unprocessed API response:
villages-events --rawThis outputs the complete JSON response from the API, including all fields and metadata. Useful for:
- Exploring available data fields
- Debugging API responses
- Planning future features
Note: When --raw is used, the --format option is ignored.
You can combine date range, category, location, format, and fields options:
# Get next week's entertainment events in JSON format
villages-events --date-range next-week --format json
# Get tomorrow's sports events at Brownwood in CSV format with custom fields
villages-events --date-range tomorrow --category sports --location Brownwood+Paddock+Square --format csv --fields location.title,title,start.date
# Get all recreation events (any date, any location) in plain text format
villages-events --date-range all --category recreation --location all --format plain
# Get today's arts and crafts events at Sawgrass Grove with detailed information
villages-events --category arts-and-crafts --location Sawgrass+Grove --fields title,location.title,description,url --format json
# Get this week's events from all categories at Spanish Springs with times
villages-events --date-range this-week --category all --location Spanish+Springs+Town+Square --fields location.title,title,start.date,end.date --format csvUse in shell scripts or pipelines:
#!/bin/bash
# Get today's events
events=$(villages-events --format meshtastic)
echo "Today's events: $events"
# Get this week's events in JSON
villages-events --date-range this-week --format json > this_week.jsonThe application supports a YAML configuration file (config.yaml) to set default values for all parameters. This eliminates the need to specify command-line arguments for your common use cases.
Create a configuration file:
cp config.yaml.example config.yamlExample configuration:
# Set your preferred defaults
format: json
date_range: this-week
category: sports
location: Brownwood+Paddock+Square
# Customize which fields to include in output
output_fields:
- location.title
- title
- start.date
- category
# Customize venue abbreviations
venue_mappings:
Brownwood: BW
Sawgrass: SG
Spanish Springs: SS
Lake Sumter: LS
# Adjust HTTP timeout
timeout: 15Using the configuration:
- If
config.yamlexists, its values become the new defaults - Command-line arguments override config file settings
- If no config file exists, hardcoded defaults are used
Example:
# With config.yaml setting format: json and category: sports
villages-events # Uses JSON format and sports category
villages-events --format csv # Overrides to CSV, still uses sports category
villages-events --category all # Uses JSON format, overrides to all categoriesCustomize which fields from the API response are included in your output by setting output_fields in config.yaml:
output_fields:
- location.title
- title
- start.date
- description
- urlAvailable fields: See the complete list in the Configurable Output Fields section above.
Field notation: Use dot notation for nested fields (e.g., location.title, start.date, address.locality).
Default behavior: If not specified, defaults to ["location.title", "title"] for backward compatibility.
Override: Command-line --fields argument takes precedence over config file settings.
Venue name abbreviations can be customized in the config.yaml file:
venue_mappings:
Brownwood: BW
Sawgrass: SG
Spanish Springs: SS
Lake Sumter: LSThe system uses substring matching - if a venue name contains any of the keywords, it will be replaced with the corresponding abbreviation. Abbreviation is only applied to the location.title field.
The application follows a pipeline architecture:
-
Token Extraction (
src/token_fetcher.py)- Fetches the main.js file from The Villages CDN
- Extracts the
dp_AUTH_TOKENusing regex pattern matching - Reconstructs the token in "Basic " format
-
Session Establishment (
src/session_manager.py)- Visits the calendar page to establish an HTTP session
- Captures cookies required for API authentication
- Manages session lifecycle and cleanup
-
API Request (
src/api_client.py)- Makes authenticated GET request to The Villages events API
- Includes proper headers (Authorization, User-Agent, etc.)
- Validates response and parses JSON data
-
Event Processing (
src/event_processor.py)- Extracts event array from API response
- Applies venue abbreviation rules
- Handles missing fields gracefully
-
Output Formatting (
src/output_formatter.py)- Formats processed events according to selected format
- Handles empty results appropriately for each format
.
├── README.md # This file
├── requirements.txt # Python dependencies
├── villages_square_events.sh # Original shell script (reference)
├── villages_events.py # Main entry point
└── src/
├── __init__.py
├── config.py # Configuration and constants
├── exceptions.py # Custom exception classes
├── token_fetcher.py # Token extraction logic
├── session_manager.py # Session and cookie management
├── api_client.py # API request handling
├── event_processor.py # Event processing and venue abbreviation
└── output_formatter.py # Output formatting logic
0- Success1- Runtime error (network failure, API error, parsing error)2- Invalid command-line arguments
Symptom: Empty output or format-appropriate empty data (single "#" for Meshtastic, "[]" for JSON)
Possible Causes:
- No events scheduled for today at town squares
- API returned empty events array
Solution: This is normal behavior when no events are scheduled. Try again on a different day.
Symptom: Error message "Failed to fetch authentication token"
Possible Causes:
- Network connectivity issues
- The Villages CDN is unavailable
- JavaScript file structure changed
Solutions:
- Check your internet connection
- Verify you can access https://cdn.thevillages.com in a browser
- If the issue persists, the JavaScript file structure may have changed - check
src/token_fetcher.pyregex pattern
Symptom: Warning about session/cookie handling
Possible Causes:
- Network issues
- Calendar page unavailable
Solutions:
- Check network connectivity
- The application will attempt to proceed anyway - if API request succeeds, no action needed
- If API request also fails, verify https://www.thevillages.com/calendar/ is accessible
Symptom: Error message "API request failed" with HTTP status code
Possible Causes:
- Invalid authentication token
- API endpoint changed
- Network issues
- Rate limiting
Solutions:
- Check network connectivity
- Verify the API URL in
src/config.pyis correct - If you're running the script frequently, wait a few minutes (possible rate limiting)
- Check if The Villages API structure has changed
Symptom: Error message about invalid JSON or missing fields
Possible Causes:
- API response structure changed
- Corrupted response data
Solutions:
- Check if The Villages API response structure has changed
- Review
src/event_processor.pyto ensure field extraction matches current API structure - Run with verbose logging (if implemented) to see raw API response
Symptom: ModuleNotFoundError or ImportError
Possible Causes:
- Dependencies not installed
- Wrong Python version
Solutions:
- Ensure you've run
pip install -r requirements.txt - Verify Python version:
python --version(should be 3.8+) - Try using
python3instead ofpythonif you have multiple Python versions
Symptom: Permission denied when running the script
Solutions:
- Ensure the script has execute permissions:
chmod +x villages_events.py - Or run with:
villages-events
- Clone the repository:
git clone https://github.com/yourusername/villages-event-scraper.git
cd villages-event-scraper- Create a virtual environment:
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate- Install development dependencies:
make install-devRun all tests:
make testRun tests with coverage:
make test-covRun specific test file:
python3 -m unittest tests.test_token_fetcher -vFormat code with Black:
make formatRun linters:
make lint- API Documentation - Detailed module and function documentation
- Architecture - System design and architecture overview
- Testing Guide - Comprehensive testing documentation
- Contributing Guide - Guidelines for contributors
To add a new output format:
- Add a new static method to
OutputFormatterclass insrc/output_formatter.py - Update the
format_events()dispatcher method - Add the format name to
VALID_FORMATSinsrc/config.py - Update this README with usage examples
This project follows Semantic Versioning. Check the current version:
villages-events --versionSee CHANGELOG.md for version history and docs/VERSIONING.md for detailed versioning information.
Contributions are welcome! Please see docs/CONTRIBUTING.md for guidelines.
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests
python -m unittest discover tests -v
# Format code
black .
# Run linting
pylint src/ villages_events.pyThis project uses Renovate to automatically keep dependencies up to date. Renovate will:
- Check for updates weekly
- Create PRs for dependency updates
- Auto-merge minor and patch updates after CI passes
- Require manual review for major updates
See docs/VERSIONING.md for more information.
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Contributions are welcome! Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
Key guidelines:
- Code follows existing style and structure
- New features include appropriate error handling
- Documentation is updated accordingly
- Tests are added for new functionality
- All tests pass and linters are happy
See CHANGELOG.md for a list of changes and version history.
For issues or questions:
- Check the Troubleshooting section above
- Review the documentation for detailed information
- Review the source code comments for implementation details
- Verify your Python version and dependencies are correct
- Open an issue on GitHub with details about your problem
- Original shell script implementation that inspired this project
- The Villages for providing the public API
- Contributors and users of this tool