Metadata-Version: 2.3
Name: tellimer
Version: 0.5.1
Summary: Python SDK for the Tellimer API
Author: Justin Godden
Author-email: Justin Godden <justin.godden@tellimer.com>
Requires-Dist: httpx==0.24.1
Requires-Dist: pandas>=2.0.0
Requires-Dist: tenacity>=9.1.2
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# Tellimer Python SDK

A Python client library for accessing Tellimer's data through their REST API. This SDK provides convenient access to macroeconomic data, parallel FX data, probability of default data, news, and other financial datasets.

## Installation

```bash
pip install tellimer
```

## Quick Start

```python
import tellimer

# Initialize the client with your API key
api_key = "your_api_key_here"
client = tellimer.Client(api_key=api_key)

# Get macroeconomic data
result = client.data.macro_data.get(
    countries=["ARG"],
    indicators=["PCPIPCH"],  # Inflation rate
    start_date="2020-01-01",
    end_date="2023-12-31"
)

result.data

# Search for news semantically, then fetch the full items
hits = client.news.search("Eurobond restructuring", limit=5)
items = client.news.get(news_ids=[h["id"] for h in hits[:2]])
```

## Authentication

The SDK requires an API key for authentication. You can obtain an API key from your Tellimer account dashboard.

```python
client = tellimer.Client(
    api_key="your_api_key_here",
    timeout=30  # Optional: request timeout in seconds
)
```

## Core Components

### Client

The main client class that provides access to all API endpoints.

#### Parameters
- `api_key` (str): Your Tellimer API key
- `timeout` (float, optional): Request timeout in seconds (default: 30)

### Result

A data class that contains the response from data requests.

#### Attributes
- `data` (pandas.DataFrame): The requested data in DataFrame format with multi-level columns
- `metadata` (list[dict]): Metadata about the indicators and countries

## Data Access

### Available Datasets

The SDK currently supports the following datasets:

- `macro_data`: Macroeconomic indicators (searchable)
- `parallel_fx`: Parallel foreign exchange data
- `probability_default`: Probability of default data with optional contribution breakdowns

```python
# List all available datasets
datasets = client.data.list_datasets()
datasets  # ['macro_data', 'parallel_fx']
```

### Macroeconomic Data (`macro_data`)

The macro data client provides access to searchable macroeconomic indicators.

#### Search Indicators

Search for available indicators using natural language queries:

```python
# Basic search
indicators = client.data.macro_data.search(
    query="inflation",
    limit=5
)
```

```python
# Search with filters
from tellimer import Filter

source_filter = Filter(field="source_name").equals("IMFWEO")
country_filter = Filter(field="country_iso").equals("ARG")

indicators = client.data.macro_data.search(
    query="inflation",
    limit=10,
    filters=[source_filter, country_filter]
)
```

#### Get Data

Retrieve actual data for specific indicators and countries:

```python
# Get data for specific indicators and countries
result = client.data.macro_data.get(
    countries=["ARG", "BRA", "MEX"],
    indicators=["PCPIPCH", "NGDP_RPCH"],  # Inflation and GDP growth
    start_date="2020-01-01",
    end_date="2023-12-31"
)

# Access the data
df = result.data  # pandas DataFrame with multi-level columns
metadata = result.metadata  # List of indicator metadata
```

### Parallel FX Data (`parallel_fx`)

Access parallel foreign exchange rate data:

```python
# Get available countries for parallel FX data
countries = client.data.parallel_fx.available_countries()

# Get parallel FX data
result = client.data.parallel_fx.get(
    countries=["ARG", "PAK"],
    start_date="2025-07-01",
    end_date="2025-09-01"
)

result.data.head()
```

### Probability of Default Data (`probability_default`)

Access probability of default data across multiple tenor horizons (1-year, 3-year, 5-year) with optional contribution breakdowns:

```python
# Get available countries for probability of default data
countries = client.data.probability_default.available_countries()

# Get 1-year probability of default (default)
result = client.data.probability_default.get(
    countries=["ARG", "BRA"],
    start_date="2024-01-01",
    end_date="2024-12-31"
)

result.data.head()
# DataFrame with columns: date, probability_of_default, country
```

#### Tenor Horizons

The `tenors` parameter controls which PD horizons to fetch. It accepts a single tenor string or a list of tenors. Valid values: `"1y"`, `"3y"`, `"5y"`. Defaults to `"1y"`.

```python
# Single tenor (3-year)
result = client.data.probability_default.get(
    countries=["ARG"],
    tenors="3y"
)
# DataFrame columns: date, probability_of_default_3y, country

# Multiple tenors
result = client.data.probability_default.get(
    countries=["ARG"],
    tenors=["1y", "3y", "5y"]
)
# DataFrame columns: date, probability_of_default, probability_of_default_3y, probability_of_default_5y, country
```

| Tenor | DataFrame Column |
|-------|-----------------|
| `"1y"` | `probability_of_default` |
| `"3y"` | `probability_of_default_3y` |
| `"5y"` | `probability_of_default_5y` |

> **Note:** 3-year and 5-year tenors require the `pd_multiyear:read` scope. If your API key lacks this permission, the API returns a 403 error indicating the missing scope.

#### Including Contribution Breakdowns

You can request additional contribution data that breaks down the probability of default into component factors. Contributions are shared across all tenors:

```python
# Get probability of default with contribution breakdowns
result = client.data.probability_default.get(
    countries=["ARG"],
    include_contributions=True,
    start_date="2024-01-01",
    end_date="2024-12-31"
)

df = result.data
print(df.columns.tolist())
# ['date', 'probability_of_default', 'country', 'gdp_growth', 'debt_gdp', ...]
```

#### Fetching All Countries

If no countries are specified, data for all available countries is returned:

```python
# Get probability of default data for all available countries
result = client.data.probability_default.get()
result.data.head()
```

## News

The `news` client exposes two methods:

- `client.news.search(...)` — semantic search over news content, powered by Weaviate. Use this to discover relevant items.
- `client.news.get(...)` — fetch full news items by ID(s), country code(s), and/or a `published_at` date range.

The typical workflow is **search → get**: run a semantic query to surface relevant items, then call `get()` with the IDs you care about (see [Example 8](#example-8-news-discovery-and-retrieval)).

### Get News

`get()` fetches news filtered by any combination of IDs, country codes, and a published-date range. At least one of `news_ids`, `countries`, `start_date`, or `end_date` must be provided. Results are returned newest first.

```python
# --- By ID ---
items = client.news.get(news_ids="abc-123")                  # single (string ok)
items = client.news.get(news_ids=["abc-123", "def-456"])     # multiple

# --- By country (ISO alpha-3) ---
items = client.news.get(countries="NGA")                     # single
items = client.news.get(countries=["NGA", "AGO"])            # multiple

# --- By date range ---
items = client.news.get(start_date="2026-05-11")                          # everything since
items = client.news.get(start_date="2026-05-01", end_date="2026-05-14")   # explicit window

# --- Combined: today's news from Nigeria and Angola ---
items = client.news.get(
    countries=["NGA", "AGO"],
    start_date="2026-05-14",
    end_date="2026-05-14",
)
```

You generally won't know news IDs ahead of time — they come from `search()` results (see below).

#### Parameters

- `news_ids` (`str | list[str] | None`): A single news ID or list of IDs.
- `countries` (`str | list[str] | None`): ISO alpha-3 country code(s) (e.g. `"NGA"`, `["NGA", "AGO"]`).
- `start_date` (`str | date | datetime | None`): Earliest `published_at` (inclusive). Accepts `YYYY-MM-DD`, `YYYY/MM/DD`, or `date`/`datetime` objects.
- `end_date` (`str | date | datetime | None`): Latest `published_at` (inclusive). Same formats as `start_date`.
- `limit` (`int`): Maximum number of items returned (default `50`).

#### Response

`get()` returns a `list[dict]`. Each item has the following fields:

| Key | Type | Notes |
|---|---|---|
| `id` | `str` | News item ID |
| `title` | `str` | |
| `url` | `str` | Source URL |
| `language` | `str` | ISO language code |
| `original_text` | `str \| None` | Body in source language |
| `text` | `str \| None` | Translated body (when available) |
| `published_at` | `str` | ISO timestamp |
| `author` | `str \| None` | |
| `country_code` | `str \| None` | ISO alpha-3 |
| `news_source_id` | `str` | |
| `company_id` | `int \| None` | |
| `relevance` | `int` | |

### Search News

Semantic search runs your query against a Weaviate index of news content chunks and returns the most relevant items. Each result includes the news `id`, which you can pass to `get()` to fetch the full item.

```python
results = client.news.search(query="Eurobond restructuring", limit=10)

# Pluck the IDs of the top 3 hits and fetch their full content
top_ids = [r["id"] for r in results[:3]]
items = client.news.get(news_ids=top_ids)
```

#### Parameters

- `query` (`str`): The text query.
- `limit` (`int`): Maximum number of results to return (default `10`).

## Filtering

The `Filter` class allows you to create complex filters for searching indicators:

### Available Filter Operations

```python
from tellimer import Filter

# Equality
filter1 = Filter("source_name").equals("IMFWEO")
filter2 = Filter("country_iso").not_equals("NGA")
```

### Date Filtering

`get` functions allows *optional* `start_date` and `end_date` arguments, which can be iso format strings, `date` or `datetime` objects. Either or both can also be left blank:

```python
from datetime import date, datetime

# String formats
start_date = "2025-05-01" # YYYY-MM-DD
start_date = "2025/05/01" # YYYY/MM/DD

# Python date objects
start_date = date(2025, 5, 1)               
start_date = datetime(2025, 5, 1, 12, 0, 0) 

# Set to `None` or can just be left blank
end_date = None

result = client.data.parallel_fx.get(
    countries=["ARG", "PAK"],
    start_date=start_date,
    end_date=end_date,
)
```

## Working with Results

### Data Structure

The returned data is structured as a pandas DataFrame with multi-level columns:

```python
result = client.data.macro_data.get(
    countries=["ARG", "NGA"],
    indicators=["PCPIPCH", "NGDP_RPCH"]
)

# The DataFrame has a multi-level column structure
# Level 0: Indicator codes
# Level 1: Country codes
# Index: Dates

df = result.data
print(df.columns)  # MultiIndex with (indicator, country) pairs
print(df.index)    # DatetimeIndex

# Access specific series
argentina_inflation = df[("PCPIPCH", "ARG")]
nigeria_gdp_growth = df[("NGDP_RPCH", "NGA")]
```

### Metadata

Each result includes metadata about the indicators:

```python
result = client.data.macro_data.get(countries=["ARG"], indicators=["PCPIPCH"])

for meta in result.metadata:
    print(f"Indicator: {meta['indicator']}")
    print(f"Country: {meta['country']}")
    print(f"Name: {meta.get('name', 'N/A')}")
    print(f"Source: {meta.get('source_name', 'N/A')}")
    print("---")
```

## Advanced Usage

### Error Handling

The SDK provides specific error classes for different types of API errors. These errors are automatically raised based on HTTP status codes:

```python
# Import specific error classes from the errors module
from tellimer.errors import (
    AuthError,
    BadRequestError,
    ForbiddenError,
    NotFoundError,
    MethodNotAllowedError,
    RateLimitError,
    InternalServerError,
    BadGatewayError,
    ServiceUnavailableError,
    GatewayTimeoutError
)

try:
    result = client.data.macro_data.get(
        countries=["INVALID"],
        indicators=["INVALID_INDICATOR"]
    )
except AuthError:
    print("Invalid API key")
except BadRequestError:
    print("Invalid request parameters")
except NotFoundError:
    print("Resource not found")
except RateLimitError:
    print("Rate limit exceeded - please wait before making more requests")
except InternalServerError:
    print("Server error - please try again later")
except Exception as e:
    print(f"Unexpected error: {e}")
```

#### Error Types

The SDK maps HTTP status codes to specific error classes:

- `400 Bad Request` → `BadRequestError`: Invalid request parameters
- `401 Unauthorized` → `AuthError`: Invalid or missing API key  
- `403 Forbidden` → `ForbiddenError`: Access denied
- `404 Not Found` → `NotFoundError`: Resource not found
- `405 Method Not Allowed` → `MethodNotAllowedError`: HTTP method not allowed
- `429 Too Many Requests` → `RateLimitError`: Rate limit exceeded
- `500 Internal Server Error` → `InternalServerError`: Server error
- `502 Bad Gateway` → `BadGatewayError`: Bad gateway
- `503 Service Unavailable` → `ServiceUnavailableError`: Service unavailable
- `504 Gateway Timeout` → `GatewayTimeoutError`: Gateway timeout

#### Handling Specific Errors

```python
import tellimer
from tellimer.errors import AuthError, RateLimitError, NotFoundError

client = tellimer.Client(api_key=api_key)

try:
    result = client.data.macro_data.get(
        countries=["ARG"],
        indicators=["PCPIPCH"]
    )
except AuthError:
    print("Please check your API key")
except RateLimitError:
    print("Rate limit exceeded. Please wait before making more requests.")
except NotFoundError:
    print("The requested data was not found")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
```

### Custom Timeouts

Configure request timeouts for your specific needs:

```python
# Short timeout for quick operations
client = tellimer.Client(api_key=api_key, timeout=10)

# Longer timeout for large data requests
client = tellimer.Client(api_key=api_key, timeout=60)
```

### Data Export

Since results are pandas DataFrames, you can easily export to various formats:

```python
result = client.data.macro_data.get(countries=["ARG"], indicators=["PCPIPCH"])

# Export to CSV
result.data.to_csv("argentina_inflation.csv")

# Export to Excel
result.data.to_excel("argentina_inflation.xlsx")

# Export to JSON
result.data.to_json("argentina_inflation.json")
```

## Examples

### Example 1: Inflation Analysis

```python
import tellimer
import matplotlib.pyplot as plt

client = tellimer.Client(api_key=api_key)

# Get inflation data for Latin American countries
result = client.data.macro_data.get(
    countries=["ARG", "BRA", "MEX", "COL", "CHL"],
    indicators=["PCPIPCH"],  # Inflation, average consumer prices
    start_date="2020-01-01",
    end_date="2023-12-31"
)

# Plot the data
df = result.data
df.plot(kind='line', figsize=(12, 6))
plt.title('Inflation Rates - Latin America')
plt.ylabel('Inflation Rate (%)')
plt.show()
```

### Example 2: GDP Growth Comparison

```python
# Search for GDP growth indicators
indicators = client.data.macro_data.search(
    query="GDP growth real",
    limit=5
)

# Get the data
result = client.data.macro_data.get(
    countries=["ARG", "BRA", "MEX", "COL", "CHL"],
    indicators=["NGDP_RPCH"],  # Real GDP growth
    start_date="2015-01-01"
)

# Calculate average growth rates
df = result.data
avg_growth = df.mean()
print("Average GDP Growth Rates:")
print(avg_growth)
```

### Example 3: Filtered Search

```python
# Create filters for World Bank data on emerging markets
source_filter = Filter("source_name").equals("World Bank")

# Search for unemployment data
indicators = client.data.macro_data.search(
    query="unemployment rate",
    limit=10,
    filters=[source_filter]
)

print(f"Found {len(indicators)} unemployment indicators from World Bank")
```

### Example 4: Probability of Default Analysis

```python
import tellimer
import matplotlib.pyplot as plt

client = tellimer.Client(api_key=api_key)

# Get available countries for probability of default data
countries = client.data.probability_default.available_countries()
print(f"Available countries: {countries}")

# Get probability of default data for emerging market countries
result = client.data.probability_default.get(
    countries=["ARG", "BRA", "TUR", "ZAF"],
    start_date="2024-01-01",
    end_date="2024-12-31"
)

# Plot probability of default over time
df = result.data
df.pivot(index="date", columns="country", values="probability_of_default").plot()
plt.title('Probability of Default (1Y) - Emerging Markets')
plt.ylabel('Probability of Default')
plt.xlabel('Date')
plt.legend(title='Country')
plt.show()
```

### Example 5: Multi-Tenor Probability of Default Comparison

```python
import tellimer
import matplotlib.pyplot as plt

client = tellimer.Client(api_key=api_key)

# Fetch 1-year, 3-year, and 5-year PD for Argentina
result = client.data.probability_default.get(
    countries=["ARG"],
    tenors=["1y", "3y", "5y"],
    start_date="2024-01-01",
    end_date="2024-12-31"
)

df = result.data
df = df.set_index("date")
df[["probability_of_default", "probability_of_default_3y", "probability_of_default_5y"]].plot()
plt.title('Argentina - PD Term Structure')
plt.ylabel('Probability of Default')
plt.legend(["1Y", "3Y", "5Y"])
plt.show()
```

### Example 6: Probability of Default with Contribution Breakdown

```python
import tellimer

client = tellimer.Client(api_key=api_key)

# Get probability of default with contribution breakdown
result = client.data.probability_default.get(
    countries=["ARG"],
    include_contributions=True,
    start_date="2024-01-01",
    end_date="2024-06-30"
)

df = result.data
print("Available columns:")
print(df.columns.tolist())
# ['date', 'probability_of_default', 'country', 'gdp_growth', 'debt_gdp', ...]
```

### Example 7: Comparing Probability of Default Across Regions

```python
import tellimer
import pandas as pd

client = tellimer.Client(api_key=api_key)

# Get data for all available countries
result = client.data.probability_default.get(
    start_date="2024-06-01",
    end_date="2024-06-30"
)

df = result.data

# Calculate average probability of default for each country
avg_pod = df.groupby("country")["probability_of_default"].mean().sort_values(ascending=False)
print("Average Probability of Default by Country:")
print(avg_pod)

# Identify highest risk countries
high_risk = avg_pod[avg_pod > avg_pod.median()]
print(f"\nCountries with above-median risk: {high_risk.index.tolist()}")
```

### Example 8: News Discovery and Retrieval

A two-step workflow: find relevant news via semantic search, then fetch the full content for the items you care about.

```python
import tellimer

client = tellimer.Client(api_key=api_key)

# Step 1: Discover relevant items via semantic search
hits = client.news.search(
    query="emerging market sovereign debt restructuring",
    limit=10,
)

for h in hits:
    print(f"{h['id']}: {h['title']}")

# Step 2: Fetch the full content for the top 3 hits
top_ids = [h["id"] for h in hits[:3]]
items = client.news.get(news_ids=top_ids)

for item in items:
    print(f"\n--- {item['title']} ({item['country_code']}, {item['published_at']}) ---")
    print((item["text"] or item["original_text"] or "")[:500])
```

You can also skip the search step entirely if you just want a feed by country and/or date:

```python
# Today's news from Nigeria and Angola
todays = client.news.get(
    countries=["NGA", "AGO"],
    start_date="2026-05-14",
    end_date="2026-05-14",
)

# Everything published in the last 3 days (regardless of country)
recent = client.news.get(start_date="2026-05-11")
```

## API Reference

### Client Class

```python
class Client:
    def __init__(self, api_key: str, timeout: float = 30)

    # Sub-clients
    data: DataClient
    news: NewsClient
```

### DataClient Class

```python
class DataClient:
    def list_datasets() -> list[str]

    # Dataset-specific clients
    macro_data: _BaseSearchableDataset
    parallel_fx: _BaseDataset
    probability_default: ProbabilityDefaultClient
```

### NewsClient Class

```python
class NewsClient:
    def get(news_ids: list[str] | str | None = None,
            countries: list[str] | str | None = None,
            start_date: str | date | datetime | None = None,
            end_date: str | date | datetime | None = None,
            limit: int = 50) -> list[dict]
    def search(query: str, limit: int = 10) -> list[dict]
```

### Dataset Methods

```python
# For searchable datasets (macro_data)
class _BaseSearchableDataset:
    def search(query: str, limit: int = 5, filters: list[Filter] = None) -> list[dict]
    def get(countries: list[str] | str, indicators: list[str] | str = None, 
            start_date: str = None, end_date: str = None) -> Result

# For non-searchable datasets (parallel_fx)
class _BaseDataset:
    def available_countries() -> list[str]
    def get(countries: list[str] | str, start_date: str = None,
            end_date: str = None) -> Result

# For probability of default data
class ProbabilityDefaultClient:
    def available_countries() -> list[str]
    def get(countries: list[str] | str | None = None,
            include_contributions: bool = False,
            start_date: str = None, end_date: str = None,
            tenors: list["1y"|"3y"|"5y"] | str = "1y") -> Result
```

### Filter Class

```python
class Filter:
    def __init__(field: str)
    def equals(value: str) -> Filter
    def not_equals(value: str) -> Filter
    def greater_than(value: str) -> Filter
    def greater_than_or_equal_to(value: str) -> Filter
    def less_than(value: str) -> Filter
    def less_than_or_equal_to(value: str) -> Filter
```

## Requirements

- Python >= 3.10
- httpx >= 0.24.1
- pandas >= 2.0.0

## Support

For support, please contact the Tellimer team or refer to the API documentation.

## Version

Current version: 0.5.1