Metadata-Version: 2.4
Name: festivalapi
Version: 0.2.8
Summary: Python client for the Festival API — film festival data for developers
Author-email: Festival API <hello@festivalapi.com>
License-Expression: MIT
Project-URL: Homepage, https://festivalapi.com
Project-URL: Documentation, https://festivalapi.com/docs
Project-URL: Repository, https://github.com/rv888/festivalapi_com
Project-URL: Issues, https://github.com/rv888/festivalapi_com/issues
Keywords: film-festival,festival,submissions,film,api
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Festival API — Python Client

[![PyPI version](https://img.shields.io/pypi/v/festivalapi.svg)](https://pypi.org/project/festivalapi/)
[![Python](https://img.shields.io/pypi/pyversions/festivalapi.svg)](https://pypi.org/project/festivalapi/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Python client for the [Festival API](https://festivalapi.com) — film festival data for developers: **14,000+ festivals across 190+ countries**, with submission deadlines, entry fees, categories, and past winners/screened films.

```bash
pip install festivalapi
```

## Quick Start

```python
from festivalapi import FestivalAPI

client = FestivalAPI("fes_your_api_key")

# List festivals by category (supports page/per_page pagination)
festivals = client.festivals.list(category="short_film")
# Pagination: client.festivals.list(category="short_film", page=2, per_page=50)

# Filter by country (full name or code)
us = client.festivals.list(country="United States")

# Keyword search
results = client.festivals.list(q="Cambodia")

# Max entry fee + deadline filters
affordable = client.festivals.list(fee_max=25, deadline_before="2026-12-31")

# Get festival detail
festival = client.festivals.get(1)

# Get festival roster (past winners / screened films)
roster = client.festivals.roster(1)
# Pagination: client.festivals.roster(1, page=2, per_page=50)

# Get scored festivals (ranked 0-100)
scored = client.festivals.scored(min_score=70)
# Pagination: client.festivals.scored(min_score=70, page=2, per_page=50)

# List available category codes (requires auth)
categories = client.categories()
for cat in categories["results"]:
    print(f'{cat["category"]} ({cat["count"]})')

# List available countries with festival counts (requires auth)
countries = client.countries()
for c in countries["results"]:
    print(f'{c["country"]} ({c["festival_count"]})')

# Health check (no auth)
client.health()
```

## Filters

`client.festivals.list()` supports:

| Param | Description |
|---|---|
| `category` | Category code (e.g. `short_film`, `feature`, `documentary`) |
| `country` | Full country name or code (e.g. `United States`, `US`, `Australia`, `AU`) |
| `genre` | Accepted genre (e.g. `drama`, `comedy`, `horror`) |
| `deadline_before` | Submission deadline before date (`YYYY-MM-DD`) |
| `fee_max` | Maximum submission fee in USD |
| `q` | Full-text search on festival name |
| `sort` | Sort field: `name` (default), `deadline`, or `event_date` |
| `sort_dir` | Sort direction: `asc` (default) or `desc` (date sorts keep unknown dates last) |

All list endpoints support `page` and `per_page` (default `per_page` is 50).

```python
# Nearest deadlines first
upcoming = client.festivals.list(sort="deadline", sort_dir="asc")
# Latest event dates first
latest = client.festivals.list(sort="event_date", sort_dir="desc")
```

## Endpoints & Credit Costs

Festival API uses a pre-paid credit system (free 50-credit trial on signup, then packs from $20). Each call costs credits:

| Method | Endpoint | Cost |
|---|---|---|
| `client.health()` | `GET /v1/health` | Free |
| `client.festivals.list()` | `GET /v1/festivals` | 1 credit |
| `client.festivals.get(id)` | `GET /v1/festivals/{id}` | 5 credits |
| `client.festivals.roster(id)` | `GET /v1/festivals/{id}/roster` | 3 credits |
| `client.festivals.scored()` | `GET /v1/festivals/scored` | 10 credits |
| `client.categories()` | `GET /v1/categories` | 1 credit |
| `client.countries()` | `GET /v1/countries` | 1 credit |

## Categories

Call `client.categories()` to get all 30 available category codes. Returns:

```python
{
  "count": 30,
  "results": [
    {"category": "short_film", "count": 6308},
    {"category": "documentary", "count": 5344},
    {"category": "feature", "count": 4609},
    {"category": "animation", "count": 3990},
    {"category": "music_video", "count": 3176},
    {"category": "experimental", "count": 2222},
    {"category": "web_series", "count": 2183},
    {"category": "branded_content", "count": 1977},
    ...
  ]
}
```

Use the `category` field (lowercase) as the filter value — e.g. `client.festivals.list(category="horror")`.

## Countries

Call `client.countries()` to get all 192 countries with festival counts. Returns:

```python
{
  "count": 192,
  "results": [
    {"country": "United States", "festival_count": 4060},
    ...
  ]
}
```

Use the country name as the filter value — e.g. `client.festivals.list(country="United States")`.

## API Key

Sign up at [festivalapi.com](https://festivalapi.com) to get your free API key (50 free credits, no credit card). Your key starts with `fes_`. You can also set the `FESTIVALAPI_KEY` environment variable:

```python
import os
from festivalapi import FestivalAPI

client = FestivalAPI(os.environ["FESTIVALAPI_KEY"])
```

## Error Handling

```python
from festivalapi import (
    FestivalAPI,
    AuthenticationError,
    InsufficientCreditsError,
    NotFoundError,
    RateLimitError,
    ServerError,
)

client = FestivalAPI("fes_your_api_key")

try:
    festival = client.festivals.get(999999)
except NotFoundError:
    print("Festival not found")
except InsufficientCreditsError as e:
    print(f"Need more credits: {e}")
```

When your credit balance hits zero, API calls return HTTP 402 with your current balance and the required credits. Handle `InsufficientCreditsError` by alerting the user or pausing calls until credits are purchased.

## Full API Reference

- Interactive docs: [festivalapi.com/docs](https://festivalapi.com/docs)
- OpenAPI spec: [festivalapi.com/v1/openapi.json](https://festivalapi.com/v1/openapi.json)

## Requirements

- Python 3.9+
- Zero dependencies (stdlib `urllib` only)

## License

MIT
