Source code for src.utilities

"""General-purpose helpers shared across the forecast tool.

A grab-bag of small utilities used by the downloaders, processors, and
notebooks:

- :func:`check_url_exists` — test whether a remote resource is reachable
- :func:`get_first_forecast_month` — derive the operational forecast month
  from a reference date
- :func:`get_date_range` — resolve the start/end dates for a CFS download,
  either automatically (from the database) or from manual input
- :func:`create_directory` — create a directory if it does not exist
- :func:`get_files` — list files in a directory matching a prefix or suffix
"""

import requests
from datetime import datetime
import pandas as pd
import os

[docs] def check_url_exists(url): """ Check whether a URL exists and is reachable via a HEAD request. Parameters ---------- url : str The full URL to be checked. Returns ------- bool True if the URL returns status code 200, False otherwise. """ try: response = requests.head(url, allow_redirects=True, timeout=10) return response.status_code == 200 except requests.RequestException as e: print(f"URL check failed: {e}") return False
from datetime import datetime from dateutil.relativedelta import relativedelta
[docs] def get_first_forecast_month(today=None): """ Returns the first forecast month based on today's date, then shifts backward by a specified number of months. Rules ----- - If today's day is before the 26th, use the current month. - If today's day is on or after the 26th, use the following month. - Then subtract `months_back` months. Parameters ---------- today : datetime, optional Reference date. Defaults to current date if None. Returns ------- str Forecast month in 'YYYY-MM' format. """ if today is None: today = datetime.today() # Determine operational forecast month if today.day < 26: forecast_month = datetime(today.year, today.month, 1) else: forecast_month = ( datetime(today.year, today.month, 1) + relativedelta(months=1) ) formatted_month = forecast_month.strftime('%m-%Y') print(f"First forecast month: {formatted_month}") return formatted_month
[docs] def get_date_range(db=None, auto='yes', start_date=None, end_date=None): """ Determine the start and end dates for CFS CSV downloads, and return the date range. Parameters ---------- db : object, optional Database object with a `get_next_run()` method. Required if auto='yes'. auto : str, default 'yes' Whether to automatically fetch the next run date from the database ('yes' or 'no'). start_date : str, optional Manual start date in format 'MM-DD-YYYY'. Required if auto='no'. end_date : str, optional Manual end date in format 'MM-DD-YYYY'. Required if auto='no'. Returns ------- tuple (start_date: datetime, end_date: datetime, date_array: pd.DatetimeIndex) """ if auto.lower() == 'yes': if db is None: raise ValueError("Database object must be provided when auto='yes'.") # Fetch next cfs_run date and use yesterday's date for the end start_date = db.get_next_run() end_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=1) else: # Convert manual input strings to datetime start_date = datetime.strptime(start_date, "%m-%d-%Y") end_date = datetime.strptime(end_date, "%m-%d-%Y") # Validate dates if start_date == end_date: print("The CSV files are up-to-date.") elif start_date > end_date: raise ValueError("End date cannot be older than start date. Try again.") else: print(f"Starting from: {start_date.strftime('%m-%d-%Y')} and continuing through: {end_date.strftime('%m-%d-%Y')}") # Create a daily date range date_array = pd.date_range(start=start_date, end=end_date, freq='1d') return start_date, end_date, date_array
[docs] def create_directory(directory): """Create a directory if it doesn't already exist.""" try: if not os.path.exists(directory): os.makedirs(directory) print(f"Directory '{directory}' created.") else: print(f"Directory '{directory}' already exists.") except PermissionError: print(f"Permission denied: Unable to create the directory '{directory}'.") except Exception as e: print(f"ERROR occurred while creating the directory '{directory}': {e}")
[docs] def get_files(directory, affix, identifier): """ List files in a directory whose names match a given prefix or suffix. Parameters ---------- directory : str Path to the directory to search. affix : str Which part of the filename to match against: ``'prefix'`` or ``'suffix'``. identifier : str The prefix or suffix string to match. Returns ------- list of str Full paths (``directory`` joined with the filename) of all matching files. Empty if nothing matches. """ files = [] for file_name in os.listdir(directory): if affix == 'suffix': if file_name.endswith(identifier): files.append(os.path.join(directory, file_name)) elif affix == 'prefix': if file_name.startswith(identifier): files.append(os.path.join(directory, file_name)) return files