Skip to content

Commit 193ea5e

Browse files
authored
Merge pull request #581 from jupyter-naas/exchangeratesapi
feat: add module to integrate exchangeratesapi
2 parents a11062c + e329700 commit 193ea5e

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Exchange Rates API Module
2+
3+
## Overview
4+
5+
### Description
6+
7+
The Exchange Rates API Module provides comprehensive integration with Exchangeratesapi.io service for currency exchange rate data.
8+
9+
This module enables:
10+
- Retrieving current exchange rates for any supported currency
11+
- Accessing historical exchange rates for specific dates
12+
- Listing all available currency symbols and their names
13+
- Automatic caching of responses to improve performance and reduce API calls
14+
- LangChain tool integration for AI agent usage
15+
16+
### Requirements
17+
18+
API Key Setup:
19+
1. Obtain an API key from [Exchangeratesapi.io](https://exchangeratesapi.io/)
20+
2. Configure your `EXCHANGERATESAPI_API_KEY` in your environment
21+
22+
### TL;DR
23+
24+
To get started with the Exchange Rates API module:
25+
26+
1. Obtain an API key from [Exchangeratesapi.io](https://exchangeratesapi.io/)
27+
2. Configure your `EXCHANGERATESAPI_API_KEY` in your environment
28+
29+
Test the integration using:
30+
```bash
31+
uv run python -m pytest src/marketplace/modules/applications/exchangeratesapi/integrations/ExchangeratesapiIntegration_test.py
32+
```
33+
34+
### Structure
35+
36+
```
37+
src/marketplace/modules/applications/exchangeratesapi/
38+
├── integrations/
39+
│ ├── ExchangeratesapiIntegration.py # Main integration class
40+
│ └── ExchangeratesapiIntegration_test.py # Integration tests
41+
├── __init__.py # Module requirements check
42+
└── README.md # This documentation
43+
```
44+
45+
## Core Components
46+
47+
The module provides a single, focused integration for accessing exchange rate data from Exchangeratesapi.io.
48+
49+
### Integrations
50+
51+
#### Exchange Rates API Integration
52+
53+
The `ExchangeratesapiIntegration` class provides methods to interact with the Exchangeratesapi.io API endpoints, handling authentication and request management.
54+
55+
**Core Functions:**
56+
- `list_symbols()`: Get all available currency symbols and names
57+
- `get_exchange_rates()`: Get exchange rates for a specific date and base currency
58+
59+
##### Configuration
60+
61+
```python
62+
from src.marketplace.modules.applications.exchangeratesapi.integrations.ExchangeratesapiIntegration import (
63+
ExchangeratesapiIntegration,
64+
ExchangeratesapiIntegrationConfiguration
65+
)
66+
67+
# Create configuration
68+
config = ExchangeratesapiIntegrationConfiguration(
69+
api_key="your_api_key_here"
70+
)
71+
72+
# Initialize integration
73+
integration = ExchangeratesapiIntegration(config)
74+
```
75+
76+
##### Usage Examples
77+
78+
**Get Current Exchange Rates:**
79+
```python
80+
# Get latest rates with EUR as base currency
81+
rates = integration.get_exchange_rates(date="latest", base="EUR")
82+
83+
# Get rates for specific currencies
84+
rates = integration.get_exchange_rates(
85+
date="latest",
86+
base="USD",
87+
symbols=["EUR", "GBP", "JPY"]
88+
)
89+
90+
# Get historical rates
91+
rates = integration.get_exchange_rates(
92+
date="2024-01-15",
93+
base="EUR"
94+
)
95+
```
96+
97+
**List Available Currencies:**
98+
```python
99+
# Get all available currency symbols
100+
symbols = integration.list_symbols()
101+
print(symbols["symbols"]) # Dictionary of currency codes and names
102+
```
103+
104+
**Using with LangChain Tools:**
105+
```python
106+
from src.marketplace.modules.applications.exchangeratesapi.integrations.ExchangeratesapiIntegration import as_tools
107+
108+
# Convert integration to LangChain tools
109+
tools = as_tools(config)
110+
111+
# The tools will be available as:
112+
# - exchangeratesapi_get_exchange_rates
113+
# - exchangeratesapi_list_symbols
114+
```
115+
116+
#### Testing
117+
118+
Run the integration tests to verify functionality:
119+
```bash
120+
uv run python -m pytest src/marketplace/modules/applications/exchangeratesapi/integrations/ExchangeratesapiIntegration_test.py
121+
```
122+
123+
**Test Coverage:**
124+
- `test_list_symbols`: Verifies currency symbol retrieval
125+
- `test_get_exchange_rates`: Tests exchange rate fetching with specific parameters
126+
127+
## API Endpoints
128+
129+
The module supports the following Exchangeratesapi.io endpoints:
130+
131+
- **GET /symbols**: List all available currencies
132+
- **GET /{date}**: Get exchange rates for a specific date (use "latest" for current rates)
133+
134+
### Supported Parameters
135+
136+
**get_exchange_rates():**
137+
- `date` (str): Date in YYYY-MM-DD format or "latest" for current rates
138+
- `base` (str): Base currency code (default: "EUR")
139+
- `symbols` (list[str]): List of target currency codes (default: all symbols)
140+
141+
**list_symbols():**
142+
- No parameters required
143+
144+
## Caching
145+
146+
The module implements intelligent caching using the ABI cache framework to:
147+
- Reduce API calls and improve performance
148+
- Cache responses based on request parameters
149+
- Store data in JSON format for easy access
150+
151+
**Cache Keys:**
152+
- Exchange rates: `get_exchange_rates_{date}_{base}_{symbols}`
153+
- Symbols: `list_symbols`
154+
155+
## Error Handling
156+
157+
The module includes comprehensive error handling:
158+
- Network connection errors
159+
- API authentication failures
160+
- Invalid request parameters
161+
- Rate limiting responses
162+
163+
All errors are wrapped in `IntegrationConnectionError` for consistent error handling across the application.
164+
165+
## Rate Limits
166+
167+
Please refer to your Exchangeratesapi.io subscription plan for rate limits:
168+
- **Free tier**: 100 requests per month
169+
- **Paid plans**: Higher limits and additional features
170+
171+
The caching mechanism helps minimize API calls and stay within rate limits.
172+
173+
## Dependencies
174+
175+
### Python Libraries
176+
- `abi.integration`: Base integration framework
177+
- `abi.services.cache`: Caching functionality
178+
- `langchain_core`: Tool integration for AI agents
179+
- `pydantic`: Data validation and serialization
180+
- `requests`: HTTP client for API calls
181+
182+
### External Services
183+
- **Exchangeratesapi.io**: Currency exchange rate data provider
184+
185+
## Module Requirements
186+
187+
The module includes a requirements check in `__init__.py` that verifies the presence of `EXCHANGERATESAPI_API_KEY` in the environment. This ensures proper configuration before the module can be used.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from src import secret
2+
3+
def requirements():
4+
api_key = secret.get('EXCHANGERATESAPI_API_KEY')
5+
if api_key:
6+
return True
7+
return False
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from abi.integration.integration import Integration, IntegrationConfiguration, IntegrationConnectionError
2+
from dataclasses import dataclass
3+
from langchain_core.tools import StructuredTool, BaseTool
4+
from pydantic import BaseModel, Field
5+
from typing import Dict
6+
from abi.services.cache.CacheFactory import CacheFactory
7+
from lib.abi.services.cache.CachePort import DataType
8+
import requests
9+
10+
cache = CacheFactory.CacheFS_find_storage(subpath="exchangeratesapi")
11+
12+
@dataclass
13+
class ExchangeratesapiIntegrationConfiguration(IntegrationConfiguration):
14+
"""Configuration for Exchangeratesapi integration.
15+
16+
Attributes:
17+
api_key (str): Exchangeratesapi API key for authentication
18+
"""
19+
api_key: str
20+
base_url: str = "https://api.exchangeratesapi.io/v1"
21+
22+
class ExchangeratesapiIntegration(Integration):
23+
"""Exchangeratesapi integration class for interacting with Exchangeratesapi's API.
24+
25+
This class provides methods to interact with Exchangeratesapi's API endpoints.
26+
It handles authentication and request management.
27+
28+
Attributes:
29+
__configuration (ExchangeratesapiIntegrationConfiguration): Configuration instance
30+
containing necessary credentials and settings.
31+
"""
32+
33+
__configuration: ExchangeratesapiIntegrationConfiguration
34+
35+
def __init__(self, configuration: ExchangeratesapiIntegrationConfiguration):
36+
"""Initialize OpenAI client with configuration."""
37+
super().__init__(configuration)
38+
self.__configuration = configuration
39+
self.params = {
40+
"access_key": self.__configuration.api_key
41+
}
42+
43+
def _make_request(self, method: str, endpoint: str, params: Dict = {}) -> Dict:
44+
"""Make HTTP request to Exchangeratesapi API."""
45+
url = f"{self.__configuration.base_url}{endpoint}"
46+
try:
47+
response = requests.request(
48+
method=method,
49+
url=url,
50+
params={**self.params, **(params or {})},
51+
)
52+
response.raise_for_status()
53+
return response.json() if response.content else {}
54+
except requests.exceptions.RequestException as e:
55+
raise IntegrationConnectionError(f"Exchangeratesapi API request failed: {str(e)}")
56+
57+
@cache(lambda self: "list_symbols", cache_type=DataType.JSON)
58+
def list_symbols(self) -> Dict:
59+
"""List symbols."""
60+
return self._make_request("GET", "/symbols")
61+
62+
@cache(lambda self, date, base, symbols: f"get_exchange_rates_{date}_{base}_{('ALL' if not symbols else ','.join(symbols))}", cache_type=DataType.JSON)
63+
def get_exchange_rates(self, date: str = "latest", base: str = "EUR", symbols: list[str] = []) -> Dict:
64+
"""Get exchange rates.
65+
66+
Args:
67+
date: The date to get the exchange rates for in format YYYY-MM-DD. Default is "latest".
68+
base: The base currency to get the exchange rates for. Default is "EUR".
69+
symbols: The symbols to get the exchange rates for. Default is all symbols.
70+
"""
71+
params = {
72+
"base": base,
73+
}
74+
if symbols:
75+
params["symbols"] = ",".join(symbols)
76+
return self._make_request("GET", f"/{date}", params)
77+
78+
def as_tools(configuration: ExchangeratesapiIntegrationConfiguration) -> list[BaseTool]:
79+
"""Convert Exchangeratesapi integration into LangChain tools."""
80+
integration = ExchangeratesapiIntegration(configuration)
81+
82+
class GetExchangeRatesSchema(BaseModel):
83+
date: str = Field(description="The date to get the exchange rates for in format YYYY-MM-DD. Default is 'latest'.")
84+
base: str = Field(description="The base currency to get the exchange rates for. Default is 'EUR'.")
85+
symbols: list[str] = Field(description="The symbols to get the exchange rates for. Default is all symbols.")
86+
87+
class ListSymbolsSchema(BaseModel):
88+
pass
89+
90+
return [
91+
StructuredTool(
92+
name="exchangeratesapi_get_exchange_rates",
93+
description="Get exchange rates for a given date, base and symbols.",
94+
func=lambda **kwargs: integration.get_exchange_rates(**kwargs),
95+
args_schema=GetExchangeRatesSchema
96+
),
97+
StructuredTool(
98+
name="exchangeratesapi_list_symbols",
99+
description="List all currencies symbols and their names.",
100+
func=lambda **kwargs: integration.list_symbols(**kwargs),
101+
args_schema=ListSymbolsSchema
102+
),
103+
]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pytest
2+
3+
from src.marketplace.modules.applications.exchangeratesapi.integrations.ExchangeratesapiIntegration import (
4+
ExchangeratesapiIntegration,
5+
ExchangeratesapiIntegrationConfiguration,
6+
)
7+
from src import secret
8+
9+
10+
@pytest.fixture
11+
def integration() -> ExchangeratesapiIntegration:
12+
configuration = ExchangeratesapiIntegrationConfiguration(api_key=secret.get("EXCHANGERATESAPI_API_KEY"))
13+
return ExchangeratesapiIntegration(configuration)
14+
15+
def test_list_symbols(integration: ExchangeratesapiIntegration):
16+
symbols = integration.list_symbols()
17+
18+
assert symbols is not None, symbols
19+
assert symbols.get("success") is True, symbols
20+
assert len(symbols.get("symbols")) > 0, symbols
21+
22+
def test_get_exchange_rates(integration: ExchangeratesapiIntegration):
23+
rates = integration.get_exchange_rates(date="2024-12-31", base="EUR", symbols=[])
24+
25+
assert rates is not None, rates
26+
assert rates.get("success") is True, rates
27+
assert rates.get("base") == "EUR", rates
28+
assert rates.get("date") == "2024-12-31", rates
29+
assert len(rates.get("rates")) > 0, rates

0 commit comments

Comments
 (0)