Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 96 additions & 17 deletions pokedex.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"""

import random
import re
import sys
import argparse
import requests
Expand All @@ -31,6 +32,11 @@
# preventing subsequent terminal output from retaining the last color.
init(autoreset=True)

# Constants for input validation
MAX_NAME_LENGTH = 50
VALID_NAME_PATTERN = re.compile(r'^[a-zA-Z0-9-]+$')
REQUEST_TIMEOUT = 10 # seconds

def parse_arguments():
"""
Parses command-line arguments provided by the user.
Expand Down Expand Up @@ -101,6 +107,51 @@ def get_random_pokemon_id():
return random.randint(1, 1025)


def validate_pokemon_input(identifier):
"""
Validates user input for Pokémon name or ID.

Checks for common input errors such as empty strings, excessively long inputs,
and invalid characters. Provides helpful error messages to guide the user.

Args:
identifier (str): The user-provided Pokémon name or ID string.

Returns:
tuple: A tuple of (is_valid, error_message). If valid, error_message is None.
If invalid, is_valid is False and error_message contains a user-friendly
description of the problem.
"""
if identifier is None:
return False, "No Pokémon name or ID provided."

identifier_str = str(identifier).strip()

if not identifier_str:
return False, "Pokémon name or ID cannot be empty."

if len(identifier_str) > MAX_NAME_LENGTH:
return False, (f"Input is too long ({len(identifier_str)} characters). "
f"Pokémon names are typically under {MAX_NAME_LENGTH} characters.")

# Allow numeric IDs
if identifier_str.isdigit():
pokemon_id = int(identifier_str)
if pokemon_id < 1:
return False, "Pokédex ID must be a positive number (1 or greater)."
if pokemon_id > 10000:
return False, (f"Pokédex ID {pokemon_id} seems too high. "
"Valid IDs are typically between 1 and 1025.")
return True, None

# Validate name format
if not VALID_NAME_PATTERN.match(identifier_str):
return False, ("Invalid characters in Pokémon name. "
"Names should only contain letters, numbers, and hyphens.")

return True, None


def fetch_pokemon_data(identifier):
"""
Fetches Pokémon data from the PokeAPI.
Expand All @@ -122,18 +173,37 @@ def fetch_pokemon_data(identifier):
"""
api_url = f"https://pokeapi.co/api/v2/pokemon/{identifier}"
try:
response = requests.get(api_url)

response = requests.get(api_url, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError:

print(f"{Fore.RED}Error: Pokémon '{identifier}' not found.")
print(f"{Fore.RED}Please check the spelling or ID and try again.{Style.RESET_ALL}")
except requests.exceptions.Timeout:
print(f"{Fore.RED}Error: Request timed out after {REQUEST_TIMEOUT} seconds.")
print(f"{Fore.YELLOW}Tip: The PokéAPI server might be slow. Please try again later.{Style.RESET_ALL}")
return None
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code if e.response is not None else None
if status_code == 404:
print(f"{Fore.RED}Error: Pokémon '{identifier}' not found.")
print(f"{Fore.YELLOW}Tip: Check the spelling or try a different name/ID.")
print(f" Example names: pikachu, charizard, bulbasaur")
print(f" Example IDs: 25 (Pikachu), 6 (Charizard), 1 (Bulbasaur){Style.RESET_ALL}")
elif status_code == 429:
print(f"{Fore.RED}Error: Too many requests. You've been rate limited.")
print(f"{Fore.YELLOW}Tip: Please wait a few seconds before trying again.{Style.RESET_ALL}")
elif status_code and status_code >= 500:
print(f"{Fore.RED}Error: The PokéAPI server is experiencing issues (HTTP {status_code}).")
print(f"{Fore.YELLOW}Tip: This is a server-side problem. Please try again later.{Style.RESET_ALL}")
else:
print(f"{Fore.RED}Error: HTTP error occurred (status code: {status_code}).")
print(f"{Fore.YELLOW}Tip: Please check your input and try again.{Style.RESET_ALL}")
return None
except requests.exceptions.ConnectionError:
print(f"{Fore.RED}Error: Could not connect to the PokéAPI.")
print(f"{Fore.YELLOW}Tip: Please check your internet connection and try again.{Style.RESET_ALL}")
return None
except requests.exceptions.RequestException as e:

print(f"{Fore.RED}Error: Could not connect to the PokéAPI. {e}{Style.RESET_ALL}")
print(f"{Fore.RED}Error: An unexpected network error occurred.")
print(f"{Fore.YELLOW}Details: {e}{Style.RESET_ALL}")
return None


Expand Down Expand Up @@ -216,29 +286,35 @@ def main():

This function orchestrates the entire program flow:
1. Parses command-line arguments using `parse_arguments`.
2. Determines the Pokémon identifier based on user input (random, ID, or name).
3. Fetches the Pokémon data from PokeAPI using `fetch_pokemon_data`.
4. Displays the retrieved information using `display_pokemon_info`,
2. Validates user input for potential errors.
3. Determines the Pokémon identifier based on user input (random, ID, or name).
4. Fetches the Pokémon data from PokeAPI using `fetch_pokemon_data`.
5. Displays the retrieved information using `display_pokemon_info`,
respecting the user's choices for displaying abilities and size.
5. Provides clear usage instructions and exits if no valid Pokémon
6. Provides clear usage instructions and exits if no valid Pokémon
identifier or lookup method is provided.
"""
args = parse_arguments()

identifier = None
if args.random:

identifier = get_random_pokemon_id()
elif args.number:

# Validate numeric input
is_valid, error_msg = validate_pokemon_input(args.number)
if not is_valid:
print(f"{Fore.RED}Error: {error_msg}{Style.RESET_ALL}")
sys.exit(1)
identifier = args.number
elif args.name:

# Validate name input
is_valid, error_msg = validate_pokemon_input(args.name)
if not is_valid:
print(f"{Fore.RED}Error: {error_msg}{Style.RESET_ALL}")
sys.exit(1)
identifier = args.name.lower()
else:

print(f"{Fore.YELLOW}Error: No Pokémon specified. Please provide a name/ID or use --random.{Style.RESET_ALL}")

parse_arguments().print_help()
sys.exit(1)

Expand All @@ -252,6 +328,9 @@ def main():
show_abilities=args.abilities,
show_size=args.size
)
else:
# Exit with error code when fetch fails
sys.exit(1)


if __name__ == "__main__":
Expand Down
Loading