Clients

AEMET

class mango.clients.aemet.AEMETClient(*, api_key: str, wait_time: float | int = None)

Bases: RESTClient

Client for accessing AEMET (Spanish Meteorological Agency) API.

This class provides access to meteorological data from weather stations across Spain. It supports retrieving historical data, live observations, and weather forecasts. An API key is required and can be obtained from the AEMET website.

Parameters:
  • api_key (str) – API key to connect to the AEMET API

  • wait_time (Union[float, int], optional) – Wait time between requests in seconds. Default is 1.25 seconds

Raises:

ValueError – If wait_time is below the minimum required value

Example:
>>> import os
>>> from mango.clients.aemet import AEMETClient
>>> client = AEMETClient(api_key=os.environ["AEMET_API_KEY"])
>>> client.connect()
>>> data = client.get_meteo_data(lat=40.4165, long=-3.70256)
connect() None

Connect to the AEMET API and cache station and municipality data.

This method establishes connection to the AEMET API, validates the API key, and caches all available weather stations and municipalities in Spain. This is a prerequisite for using other methods in the class.

Raises:
  • ApiKeyError – If the provided API key is invalid

  • Exception – If connection to the API fails

Example:
>>> client = AEMETClient(api_key="your_api_key")
>>> client.connect()
property all_stations: List[dict]

Get all meteorological stations in Spain.

Returns a list of dictionaries containing information about all available weather stations in Spain, including their codes, names, locations, and other metadata.

Returns:

List of dictionaries with meteorological station information

Return type:

List[dict]

Example:
>>> stations = client.all_stations
>>> print(f"Total stations: {len(stations)}")
property municipios: List[dict]

Get all municipalities in Spain.

Returns a list of dictionaries containing information about all municipalities in Spain, including postal codes, names, and coordinates.

Returns:

List of dictionaries with municipality information

Return type:

List[dict]

Example:
>>> municipalities = client.municipios
>>> print(f"Total municipalities: {len(municipalities)}")
get_meteo_data(station_code: str = None, lat: float = None, long: float = None, province: str = None, start_date: datetime = None, end_date: datetime = None, output_format: Literal['df', 'raw'] = 'raw')

Retrieve meteorological data from Spanish weather stations.

This is the main method for obtaining weather data. It supports multiple search criteria and can return historical or live data in different formats.

Station selection priority: 1. If station_code is provided, use only that station 2. If lat/long are provided, find the closest station (optionally in province) 3. If only province is provided, use all stations in that province 4. Otherwise, use all stations in Spain

Parameters:
  • station_code (str, optional) – Meteorological station code (e.g., “3195”)

  • lat (float, optional) – Latitude coordinate in decimal degrees

  • long (float, optional) – Longitude coordinate in decimal degrees

  • province (str, optional) – Province name to limit search scope

  • start_date (datetime, optional) – Start date for historical data retrieval

  • end_date (datetime, optional) – End date for historical data retrieval

  • output_format (Literal["df", "raw"]) – Output format - “df” for DataFrame, “raw” for list of dicts

Returns:

Meteorological data in the specified format

Return type:

Union[pandas.DataFrame, List[dict]]

Raises:
  • TypeError – If parameter types are incorrect

  • ValueError – If parameter combinations are invalid

  • Exception – If no data is found for any station

Example:
>>> from datetime import datetime
>>> # Get data from closest station to coordinates
>>> data = client.get_meteo_data(
...     lat=40.4165, long=-3.70256,
...     start_date=datetime(2021, 1, 1),
...     end_date=datetime(2021, 1, 31),
...     output_format="df"
... )
>>> # Get live data from specific station
>>> data = client.get_meteo_data(
...     station_code="3195",
...     output_format="raw"
... )
get_forecast_data(postal_code: str = None)

Retrieve weather forecast data for Spanish municipalities.

This method fetches weather forecast data from the AEMET API for the next few days. If no postal code is specified, it retrieves forecasts for all municipalities in Spain (this can take a long time due to API rate limits).

Parameters:

postal_code (str, optional) – Postal code of the municipality (e.g., “28001”)

Returns:

List of dictionaries containing forecast data

Return type:

List[dict]

Raises:
  • TypeError – If postal_code is not a string

  • AssertionError – If postal_code is not found in available municipalities

  • Exception – If no forecast data is found for any municipality

Example:
>>> # Get forecast for specific municipality
>>> forecast = client.get_forecast_data(postal_code="28001")
>>> # Get forecasts for all municipalities (slow)
>>> all_forecasts = client.get_forecast_data()
custom_endpoint(endpoint: str)

Access custom AEMET API endpoints directly.

Allows direct access to any AEMET API endpoint by providing the endpoint path. This method provides flexibility for accessing endpoints not covered by the standard methods.

Parameters:

endpoint (str) – API endpoint path (must start with “/api”)

Returns:

Raw data from the specified endpoint

Return type:

Any

Raises:
  • TypeError – If endpoint is not a string

  • ValueError – If endpoint doesn’t start with “/api” or no data is found

Example:
>>> data = client.custom_endpoint("/api/valores/climatologicos/normales/estacion/3195")
static expect_status(func, expected_status=None, response_type: Literal['json', 'raw'] = 'json')

Decorator to validate HTTP response status codes and format responses.

This decorator wraps functions that return HTTP responses and validates the status code against the expected value. It also handles response formatting to return either JSON data or raw response objects.

Parameters:
  • func (callable) – Function that returns a requests.Response object

  • expected_status (int, optional) – Expected HTTP status code (e.g., 200, 201)

  • response_type (Literal["json", "raw"]) – Format of returned data - “json” or “raw”

Returns:

Decorated function that validates status and formats response

Return type:

callable

Raises:
  • HTTPError – If response status doesn’t match expected_status

  • ValueError – If response_type is invalid

Example:
>>> @RESTClient.expect_status(expected_status=200, response_type="json")
... def get_data():
...     return requests.get("https://api.example.com/data")
static request_handler(url: str, params: dict, wait_time: float | int = 0.5, if_error: Literal['raise', 'warn', 'ignore'] = 'raise', expected_schema: Type[pydantic.BaseModel] = None) dict | List[dict]

Handle HTTP GET requests with error management and response validation.

Makes a GET request to the specified URL with the given parameters, implements rate limiting through wait time, and provides flexible error handling options. Optionally validates response against a Pydantic schema.

Parameters:
  • url (str) – URL to make the request to

  • params (dict) – Parameters to pass to the request

  • wait_time (Union[float, int]) – Wait time in seconds after the request

  • if_error (Literal["raise", "warn", "ignore"]) – Error handling strategy - “raise”, “warn”, or “ignore”

  • expected_schema (Type[BaseModel], optional) – Pydantic schema to validate response structure

Returns:

Parsed JSON response as dictionary or list of dictionaries

Return type:

Union[dict, List[dict]]

Raises:
  • HTTPError – If request fails and if_error is “raise”

  • ValueError – If if_error parameter is invalid

Example:
>>> response = RESTClient.request_handler(
...     url="https://api.example.com/data",
...     params={"key": "value"},
...     wait_time=1.0,
...     if_error="warn"
... )

ARCGIS

class mango.clients.arcgis.ArcGisClient(client_id, client_secret)

Bases: object

Client for accessing ArcGIS services including geocoding and routing.

This class provides access to ArcGIS REST API services for geocoding addresses and calculating origin-destination matrices. Authentication is required using client credentials.

Parameters:
  • client_id (str) – ArcGIS client ID for authentication

  • client_secret (str) – ArcGIS client secret for authentication

Example:
>>> client = ArcGisClient(client_id="your_client_id", client_secret="your_secret")
>>> client.connect()
>>> coords = client.get_geolocation("Madrid, Spain")
connect()

Authenticate with ArcGIS services and obtain access token.

Establishes connection to ArcGIS REST API using client credentials. The obtained access token is stored for use in subsequent API calls. This method must be called before using other methods in the class.

Raises:

InvalidCredentials – If authentication fails or credentials are invalid

Example:
>>> client = ArcGisClient(client_id="your_id", client_secret="your_secret")
>>> client.connect()
get_geolocation(address: str, country: str = 'ESP') tuple

Get the geolocation coordinates for a given address.

Uses ArcGIS geocoding service to convert an address into longitude and latitude coordinates. The service returns the best match for the provided address.

Parameters:
  • address (str) – The address to geocode

  • country (str) – Country code for the address (default: “ESP” for Spain)

Returns:

Tuple containing (longitude, latitude) coordinates

Return type:

tuple

Raises:
  • Exception – If API request fails or returns invalid status

  • JobError – If geocoding fails or no candidates are found

Example:
>>> coords = client.get_geolocation("Madrid, Spain")
>>> print(f"Longitude: {coords[0]}, Latitude: {coords[1]}")
get_origin_destination_matrix(*, mode: str = 'sync', origins: list, destinations: list, travel_mode: dict = None, sleep_time: int = 2) list

Calculate origin-destination matrix with travel times and distances.

Computes travel times and distances between multiple origins and destinations using ArcGIS routing services. Supports both synchronous and asynchronous modes. The method automatically switches to async mode for large matrices.

Parameters:
  • mode (str) – Processing mode - “sync” for immediate results, “async” for large datasets

  • origins (list) – List of origin points with keys: “name”, “x”, “y”

  • destinations (list) – List of destination points with keys: “name”, “x”, “y”

  • travel_mode (dict, optional) – Travel mode configuration (default: car travel mode)

  • sleep_time (int) – Wait time between status checks in async mode (seconds)

Returns:

List of dictionaries with origin, destination, distance (meters), and time (seconds)

Return type:

list

Raises:
  • NotImplementedError – If matrix size exceeds 1000x1000 limit

  • ValueError – If mode is invalid

Example:
>>> origins = [{"name": "Madrid", "x": -3.7038, "y": 40.4168}]
>>> destinations = [{"name": "Barcelona", "x": 2.1734, "y": 41.3851}]
>>> matrix = client.get_origin_destination_matrix(
...     origins=origins, destinations=destinations, mode="sync"
... )

EmailDownloader

class mango.clients.email_downloader.EmailDownloader(host: str = None, user: str = None, password: str = None)

Bases: object

Client for downloading emails and attachments from IMAP servers.

This class provides functionality to connect to IMAP email servers, download unread emails, and save attachments to the local filesystem. Supports SSL connections and automatic email marking as read.

Parameters:
  • host (str, optional) – IMAP server hostname (default: from EMAIL_HOST environment variable)

  • user (str, optional) – Email username (default: from EMAIL_USER environment variable)

  • password (str, optional) – Email password (default: from EMAIL_PASSWORD environment variable)

Raises:

ValueError – If any required configuration is missing

Example:
>>> downloader = EmailDownloader(
...     host="imap.gmail.com",
...     user="user@gmail.com",
...     password="password"
... )
>>> emails = downloader.fetch_unread_emails()
close_connection()

Close the connection to the IMAP email server.

Properly closes the IMAP connection to free up resources. Should be called when finished with email operations.

Example:
>>> downloader = EmailDownloader()
>>> # ... use downloader ...
>>> downloader.close_connection()
fetch_unread_emails() list

Fetch all unread emails from the IMAP server.

Downloads all unread emails from the server and automatically marks them as read. Returns a list of email message objects that can be processed further.

Returns:

List of email message objects

Return type:

list

Raises:

Exception – If IMAP operations fail

Example:
>>> emails = downloader.fetch_unread_emails()
>>> for email in emails:
...     print(f"Subject: {email.get('Subject')}")
static save_attachments(msg, download_folder: str = '.') list

Save email attachments to the local filesystem.

Extracts and saves all attachments from an email message to the specified local directory. Skips files that already exist to avoid overwriting.

Parameters:
  • msg (email.message.Message) – Email message object containing attachments

  • download_folder (str) – Local directory path to save attachments

Returns:

List of file paths where attachments were saved

Return type:

list

Example:
>>> emails = downloader.fetch_unread_emails()
>>> for email in emails:
...     attachments = EmailDownloader.save_attachments(email, "./downloads")
...     print(f"Saved {len(attachments)} attachments")

EmailSender

class mango.clients.email_sender.EmailSender(host: str = None, port: int = None, user: str = None, password: str = None)

Bases: object

Client for sending emails with attachments via SMTP.

This class provides functionality to send emails through SMTP servers with support for attachments, HTML/text content, and SSL encryption. Configuration can be provided directly or through environment variables.

Parameters:
  • host (str, optional) – SMTP server hostname (default: from EMAIL_HOST environment variable)

  • port (int, optional) – SMTP server port (default: from EMAIL_PORT environment variable)

  • user (str, optional) – Email username (default: from EMAIL_USER environment variable)

  • password (str, optional) – Email password (default: from EMAIL_PASSWORD environment variable)

Raises:

ValueError – If any required configuration is missing

Example:
>>> sender = EmailSender(
...     host="smtp.gmail.com",
...     port=465,
...     user="sender@gmail.com",
...     password="password"
... )
>>> sender.send_email_with_attachments(
...     destination="recipient@example.com",
...     subject="Test Email",
...     body="Hello World",
...     attachments=["file1.pdf", "file2.txt"]
... )
send_email_with_attachments(destination: str, subject: str, body: str = None, attachments: list = None)

Send an email with optional attachments via SMTP.

Creates and sends an email message with the specified content and attachments. Uses SSL encryption for secure transmission. Attachments are read from local file paths and attached to the email.

Parameters:
  • destination (str) – Email address of the recipient

  • subject (str) – Subject line of the email

  • body (str, optional) – Email body content (plain text)

  • attachments (list, optional) – List of file paths to attach to the email

Raises:
  • FileNotFoundError – If any attachment file is not found

  • Exception – If SMTP connection or sending fails

Example:
>>> sender.send_email_with_attachments(
...     destination="user@example.com",
...     subject="Report",
...     body="Please find the report attached.",
...     attachments=["report.pdf", "data.xlsx"]
... )

GoogleCloudStorage

Note

This class inherits from mango.clients.cloud_storage.CloudStorage which is an abstract base class. The autoclass documentation may not generate properly due to the abstract nature of the parent class.

class mango.clients.google_cloud_storage.GoogleCloudStorage(secrets_file, bucket_name)

Bases: CloudStorage

Google Cloud Storage client for managing files and objects in GCS buckets.

This class provides a comprehensive interface for Google Cloud Storage operations including uploading, downloading, deleting, copying, and moving files. It extends the abstract CloudStorage class with GCS-specific functionality.

Parameters:
  • secrets_file (str) – Path to Google Cloud service account JSON file

  • bucket_name (str) – Name of the GCS bucket to work with

Raises:

ValueError – If secrets file is missing

Example:
>>> gcs = GoogleCloudStorage(
...     secrets_file="/path/to/service-account.json",
...     bucket_name="my-bucket"
... )
>>> gcs.upload_from_filename("local_file.txt", "remote_file.txt")
upload_object(contents, blob_name: str)

Upload Python object contents to Google Cloud Storage.

Uploads raw data/contents directly to the specified blob in the GCS bucket. The contents must be convertible to string or bytes format.

Parameters:
  • contents (Union[str, bytes, object]) – The data content to upload (string, bytes, or serializable object)

  • blob_name (str) – Name/path of the blob in the GCS bucket

Example:
>>> gcs.upload_object("Hello World", "greeting.txt")
>>> gcs.upload_object(b"Binary data", "data.bin")
upload_from_filename(file_name: str, blob_name: str)

Upload file from local filesystem to Google Cloud Storage.

Reads a file from the local filesystem and uploads its contents to the specified blob in the GCS bucket.

Parameters:
  • file_name (str) – Path to the local file to upload

  • blob_name (str) – Name/path of the blob in the GCS bucket

Raises:

FileNotFoundError – If the local file does not exist

Example:
>>> gcs.upload_from_filename("/path/to/local/file.txt", "remote/file.txt")
upload_from_file(file, blob_name: str)

Upload file from an open file handle to Google Cloud Storage.

Uploads data from an already open file handle to the specified blob in the GCS bucket. The file handle must be in binary read mode.

Parameters:
  • file (file-like object) – Open file handle in binary read mode

  • blob_name (str) – Name/path of the blob in the GCS bucket

Example:
>>> with open("data.txt", "rb") as f:
...     gcs.upload_from_file(f, "remote/data.txt")
rename_file(blob_name: str, new_name: str)

Rename an existing blob in Google Cloud Storage.

Changes the name of an existing blob within the same bucket. This operation creates a new blob with the new name and deletes the old one.

Parameters:
  • blob_name (str) – Current name of the blob to rename

  • new_name (str) – New name for the blob

Raises:

NotFound – If the source blob does not exist

Example:
>>> gcs.rename_file("old_name.txt", "new_name.txt")
download_to_object(blob_name: str)

Download blob contents as a Python object from Google Cloud Storage.

Downloads the blob contents and returns them as bytes. The contents can be converted to string or other formats as needed.

Parameters:

blob_name (str) – Name of the blob to download

Returns:

Blob contents as bytes

Return type:

bytes

Raises:

NotFound – If the blob does not exist

Example:
>>> content = gcs.download_to_object("file.txt")
>>> text = content.decode('utf-8')
download_to_file(blob_name: str, destination_path: str)

Download blob from Google Cloud Storage to local filesystem.

Downloads the specified blob and saves it to the local filesystem at the given destination path.

Parameters:
  • blob_name (str) – Name of the blob to download

  • destination_path (str) – Local file path where the blob will be saved

Raises:

NotFound – If the blob does not exist

Example:
>>> gcs.download_to_file("remote/file.txt", "/local/path/file.txt")
delete_file(blob_name: str)

Delete a blob from Google Cloud Storage bucket.

Permanently removes the specified blob from the GCS bucket. This operation cannot be undone.

Parameters:

blob_name (str) – Name of the blob to delete

Raises:

NotFound – If the blob does not exist

Example:
>>> gcs.delete_file("unwanted_file.txt")
list_files()

List all files in the Google Cloud Storage bucket.

Retrieves a list of all blob names currently stored in the bucket. This includes all files regardless of their location within the bucket.

Returns:

List of blob names in the bucket

Return type:

list

Example:
>>> files = gcs.list_files()
>>> print(f"Bucket contains {len(files)} files")
copy_file(blob_name: str, destination_bucket: str = None, destination_blob_name: str = None)

Copy a blob to another bucket in Google Cloud Storage.

Creates a copy of the specified blob in the destination bucket. If no destination bucket is specified, uses the environment variable DESTINATION_BUCKET_NAME. If no new name is provided, keeps the original name.

Parameters:
  • blob_name (str) – Name of the source blob to copy

  • destination_bucket (str, optional) – Name of the destination bucket

  • destination_blob_name (str, optional) – New name for the blob in destination bucket

Raises:

NotFound – If the source blob does not exist

Example:
>>> gcs.copy_file("source.txt", "other-bucket", "copied.txt")
move_file(blob_name: str, destination_bucket: str = None, destination_blob_name: str = None)

Move a blob from one bucket to another in Google Cloud Storage.

Moves the specified blob to the destination bucket by copying it and then deleting the original. If no destination bucket is specified, uses the environment variable DESTINATION_BUCKET_NAME.

Parameters:
  • blob_name (str) – Name of the source blob to move

  • destination_bucket (str, optional) – Name of the destination bucket

  • destination_blob_name (str, optional) – New name for the blob in destination bucket

Raises:

NotFound – If the source blob does not exist

Example:
>>> gcs.move_file("source.txt", "archive-bucket", "moved.txt")
upload_from_folder(local_path: str, blob_name: str)

Upload all files from a local folder to Google Cloud Storage.

Recursively uploads all files from the specified local directory to the GCS bucket, preserving the directory structure. Files are uploaded with their relative paths as blob names.

Parameters:
  • local_path (str) – Path to the local directory to upload

  • blob_name (str) – Base path/prefix for the uploaded files in the bucket

Raises:

ValueError – If the local path is not a directory

Example:
>>> gcs.upload_from_folder("/local/data", "backup/data")

RESTClient

class mango.clients.rest_client.RESTClient

Bases: ABC

Abstract base class for REST API clients.

This class provides a foundation for implementing REST API clients with common functionality for request handling, error management, and response validation. Concrete implementations must provide their own connect method.

Example:
>>> class MyAPIClient(RESTClient):
...     def connect(self):
...         # Implementation for API connection
...         pass
abstract connect(*args, **kwargs)

Establish connection to the REST API.

This abstract method must be implemented by subclasses to establish a connection to their specific API. It can be used to verify API availability and validate authentication credentials.

Parameters:
  • args – Variable length argument list

  • kwargs – Arbitrary keyword arguments

Raises:

NotImplementedError – If not implemented in subclass

Example:
>>> client = MyAPIClient()
>>> client.connect(api_key="your_key")
static request_handler(url: str, params: dict, wait_time: float | int = 0.5, if_error: Literal['raise', 'warn', 'ignore'] = 'raise', expected_schema: Type[pydantic.BaseModel] = None) dict | List[dict]

Handle HTTP GET requests with error management and response validation.

Makes a GET request to the specified URL with the given parameters, implements rate limiting through wait time, and provides flexible error handling options. Optionally validates response against a Pydantic schema.

Parameters:
  • url (str) – URL to make the request to

  • params (dict) – Parameters to pass to the request

  • wait_time (Union[float, int]) – Wait time in seconds after the request

  • if_error (Literal["raise", "warn", "ignore"]) – Error handling strategy - “raise”, “warn”, or “ignore”

  • expected_schema (Type[BaseModel], optional) – Pydantic schema to validate response structure

Returns:

Parsed JSON response as dictionary or list of dictionaries

Return type:

Union[dict, List[dict]]

Raises:
  • HTTPError – If request fails and if_error is “raise”

  • ValueError – If if_error parameter is invalid

Example:
>>> response = RESTClient.request_handler(
...     url="https://api.example.com/data",
...     params={"key": "value"},
...     wait_time=1.0,
...     if_error="warn"
... )
static expect_status(func, expected_status=None, response_type: Literal['json', 'raw'] = 'json')

Decorator to validate HTTP response status codes and format responses.

This decorator wraps functions that return HTTP responses and validates the status code against the expected value. It also handles response formatting to return either JSON data or raw response objects.

Parameters:
  • func (callable) – Function that returns a requests.Response object

  • expected_status (int, optional) – Expected HTTP status code (e.g., 200, 201)

  • response_type (Literal["json", "raw"]) – Format of returned data - “json” or “raw”

Returns:

Decorated function that validates status and formats response

Return type:

callable

Raises:
  • HTTPError – If response status doesn’t match expected_status

  • ValueError – If response_type is invalid

Example:
>>> @RESTClient.expect_status(expected_status=200, response_type="json")
... def get_data():
...     return requests.get("https://api.example.com/data")