"""
Data models for the BCRA Currency Exchange Statistics API.
Provides classes for currency quotations, historical data, and response handling.
"""
from dataclasses import dataclass
from datetime import date
from typing import TYPE_CHECKING, Any, Dict, List, Optional
if TYPE_CHECKING:
import pandas as pd
[docs]
@dataclass
class Divisa:
"""
Represents a currency.
:param codigo: The currency code (ISO)
:param denominacion: The currency name
"""
codigo: str
denominacion: str
def __post_init__(self) -> None:
"""Validate instance after initialization."""
if not self.codigo.strip():
raise ValueError("Currency code cannot be empty")
if not self.denominacion.strip():
raise ValueError("Currency name cannot be empty")
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Divisa":
"""Create a Divisa instance from a dictionary."""
return cls(codigo=data["codigo"], denominacion=data["denominacion"])
[docs]
@dataclass
class CotizacionDetalle:
"""
Represents details of a currency quotation.
:param codigo_moneda: The currency code
:param descripcion: The currency description
:param tipo_pase: The exchange rate
:param tipo_cotizacion: The quotation type
"""
codigo_moneda: str
descripcion: str
tipo_pase: float
tipo_cotizacion: float
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CotizacionDetalle":
"""Create a CotizacionDetalle instance from a dictionary."""
return cls(
codigo_moneda=data["codigoMoneda"],
descripcion=data["descripcion"],
tipo_pase=float(data["tipoPase"]),
tipo_cotizacion=float(data["tipoCotizacion"]),
)
[docs]
@dataclass
class CotizacionFecha:
"""
Represents currency quotations for a specific date.
:param fecha: The date of the quotations
:param detalle: List of quotation details
"""
fecha: Optional[date]
detalle: List[CotizacionDetalle]
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CotizacionFecha":
"""Create a CotizacionFecha instance from a dictionary."""
return cls(
fecha=date.fromisoformat(data["fecha"]) if data["fecha"] else None,
detalle=[CotizacionDetalle.from_dict(d) for d in data["detalle"]],
)
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Convert the CotizacionFecha instance to a dictionary."""
return {
"fecha": self.fecha.isoformat() if self.fecha else None,
"detalle": [
{
"codigoMoneda": d.codigo_moneda,
"descripcion": d.descripcion,
"tipoPase": d.tipo_pase,
"tipoCotizacion": d.tipo_cotizacion,
}
for d in self.detalle
],
}
[docs]
def to_dataframe(self) -> "pd.DataFrame":
"""
Convert the CotizacionFecha instance to a pandas DataFrame.
Returns a DataFrame with exchange rate information for each currency.
Columns: fecha, codigoMoneda, descripcion, tipoPase, tipoCotizacion.
Requires pandas: ``pip install bcra-connector[pandas]``
:return: DataFrame with exchange rate data.
:raises ImportError: If pandas is not installed.
"""
try:
import pandas as pd
except ImportError:
raise ImportError(
"pandas is required for to_dataframe(). "
"Install with: pip install bcra-connector[pandas]"
)
rows = [
{
"fecha": self.fecha,
"codigoMoneda": d.codigo_moneda,
"descripcion": d.descripcion,
"tipoPase": d.tipo_pase,
"tipoCotizacion": d.tipo_cotizacion,
}
for d in self.detalle
]
return pd.DataFrame(rows)
[docs]
@dataclass
class Resultset:
"""
Represents metadata about the result set.
:param count: The number of results
:param offset: The offset of the results
:param limit: The limit of the results
"""
count: int
offset: int
limit: int
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Resultset":
"""Create a Resultset instance from a dictionary."""
return cls(count=data["count"], offset=data["offset"], limit=data["limit"])
[docs]
@dataclass
class DivisaResponse:
"""
Represents the response for the Divisas endpoint.
:param status: The HTTP status code
:param results: List of Divisa objects
"""
status: int
results: List[Divisa]
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DivisaResponse":
"""Create a DivisaResponse instance from a dictionary."""
return cls(
status=data["status"],
results=[Divisa.from_dict(d) for d in data["results"]],
)
[docs]
@dataclass
class CotizacionResponse:
"""
Represents the response for the Cotizaciones endpoint.
:param status: The HTTP status code
:param results: CotizacionFecha object
"""
status: int
results: CotizacionFecha
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CotizacionResponse":
"""Create a CotizacionResponse instance from a dictionary."""
return cls(
status=data["status"], results=CotizacionFecha.from_dict(data["results"])
)
[docs]
@dataclass
class CotizacionesResponse:
"""
Represents the response for the Cotizaciones/{codMoneda} endpoint.
:param status: The HTTP status code
:param metadata: Metadata about the response
:param results: List of CotizacionFecha objects
"""
status: int
metadata: Metadata
results: List[CotizacionFecha]
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CotizacionesResponse":
"""Create a CotizacionesResponse instance from a dictionary."""
return cls(
status=data["status"],
metadata=Metadata.from_dict(data["metadata"]),
results=[CotizacionFecha.from_dict(d) for d in data["results"]],
)
[docs]
@dataclass
class ErrorResponse:
"""
Represents an error response from the API.
:param status: The HTTP status code
:param error_messages: List of error messages
"""
status: int
error_messages: List[str]
[docs]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ErrorResponse":
"""Create an ErrorResponse instance from a dictionary."""
return cls(status=data["status"], error_messages=data["errorMessages"])