Metadata-Version: 2.4
Name: producthunt-sdk
Version: 0.1.0
Summary: Python SDK for the Product Hunt API v2
Requires-Python: >=3.13
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: tenacity>=9.1.2
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Provides-Extra: examples
Requires-Dist: python-dotenv>=1.0.0; extra == 'examples'
Description-Content-Type: text/markdown

# Product Hunt SDK

A Python SDK for the Product Hunt API. Track trending products, discover new launches, and monitor your own products.

## Quick Start

```bash
pip install producthunt-sdk
```

```python
from producthunt_sdk import ProductHuntClient, BearerAuth

client = ProductHuntClient(auth=BearerAuth("your_developer_token"))

# Get today's top products
for post in client.get_posts(featured=True, first=5).nodes:
    print(f"{post.name} - {post.votes_count} votes")
```

Get your token at [producthunt.com/v2/oauth/applications](https://www.producthunt.com/v2/oauth/applications)

## Data Access

Product Hunt's API has privacy restrictions. Here's what you can access:

| Data | Access |
|------|--------|
| Featured/trending posts | Full access |
| Post details (votes, comments count) | Full access |
| Topics and collections | Full access |
| Comments on posts | Text only (commenter info redacted) |
| **Your own** profile, posts, votes, followers | Full access |
| **Other users'** profiles, posts, votes, followers | Redacted |

For extended access to user data, contact Product Hunt at hello@producthunt.com.

## What You Can Do

### Track Your Launch Performance

```python
post = client.get_post(slug="your-product")
print(f"Votes: {post.votes_count}")
print(f"Comments: {post.comments_count}")
print(f"Rating: {post.reviews_rating}")
```

### Find Trending Products

```python
from datetime import datetime, timedelta, UTC
from producthunt_sdk import PostsOrder

# Top AI products from the last week
posts = client.get_posts(
    topic="artificial-intelligence",
    posted_after=datetime.now(UTC) - timedelta(days=7),
    order=PostsOrder.VOTES,
    first=10
)

for post in posts.nodes:
    print(f"{post.name}: {post.tagline}")
```

### Read Product Comments

```python
from producthunt_sdk import CommentsOrder

comments = client.get_post_comments(
    post_slug="chatgpt",
    first=10,
    order=CommentsOrder.VOTES_COUNT
)
for comment in comments.nodes:
    print(f"{comment.body[:100]}...")
```

### Monitor Topics and Trends

```python
from producthunt_sdk import TopicsOrder

# Most popular topics
topics = client.get_topics(order=TopicsOrder.FOLLOWERS_COUNT, first=10)
for topic in topics.nodes:
    print(f"{topic.name}: {topic.followers_count} followers, {topic.posts_count} products")
```

### Access Your Own Data

```python
# Get your profile (requires your token)
viewer = client.get_viewer()
print(f"Logged in as: @{viewer.user.username}")

# Get your own posts
my_posts = client.get_user_posts(username=viewer.user.username)
for post in my_posts.nodes:
    print(f"{post.name} - {post.votes_count} votes")
```

## Async Support

For high-performance applications:

```python
import asyncio
from producthunt_sdk import AsyncProductHuntClient, BearerAuth

async def main():
    async with AsyncProductHuntClient(auth=BearerAuth("your_token")) as client:
        posts = await client.get_posts(featured=True)
        print(f"Found {len(posts.nodes)} products")

asyncio.run(main())
```

## OAuth Authentication

Use OAuth when building apps that authenticate users. This gives you access to the authenticated user's own data.

**Note:** Product Hunt requires HTTPS redirect URIs. Use [ngrok](https://ngrok.com) to tunnel to localhost for development.

```python
from producthunt_sdk import ProductHuntClient, OAuth2

# Just create the client - OAuth flow runs automatically on first request
client = ProductHuntClient(auth=OAuth2(
    client_id="your_client_id",
    client_secret="your_client_secret",
    redirect_uri="https://your-app.ngrok.io/callback",
))

# This triggers the OAuth flow: opens browser, waits for callback, exchanges token
viewer = client.get_viewer()
print(f"Logged in as @{viewer.user.username}")

# Token is cached - subsequent requests use the cached token
posts = client.get_user_posts(username=viewer.user.username)
```

### Token Caching

Tokens are cached in memory by default. For persistence across restarts:

```python
from producthunt_sdk import OAuth2, TokenCache

# Use file-based cache
OAuth2.token_cache = TokenCache("~/.producthunt_tokens.json")
```

See `examples/oauth_flow.py` for a complete example.

## Custom GraphQL Queries

Need something specific? Use raw GraphQL:

```python
data = client.graphql("""
    query {
        posts(first: 5, featured: true) {
            edges {
                node {
                    name
                    votesCount
                }
            }
        }
    }
""")
```

---

## Installation

```bash
# With pip
pip install producthunt-sdk

# With uv
uv add producthunt-sdk
```

**Requirements:** Python 3.13+

## Configuration

```python
from producthunt_sdk import ProductHuntClient, BearerAuth

client = ProductHuntClient(
    auth=BearerAuth("your_token"),
    auto_wait_on_rate_limit=True,  # Wait instead of failing (default: True)
    max_wait_seconds=900,           # Max wait time for rate limits (default: 15min)
    timeout=30.0,                   # Request timeout (default: 30s)
    max_retries=3,                  # Retry on network errors (default: 3)
)
```

## Rate Limits

The API allows 6,250 complexity points per 15 minutes. The SDK:

- Tracks your usage automatically
- Waits when you hit the limit (configurable)
- Retries on network errors and server issues

Check your usage:

```python
info = client.rate_limit_info
print(f"Remaining: {info.remaining}/{info.limit}")
print(f"Resets in: {info.seconds_until_reset:.0f}s")
```

## Available Methods

| Method | Description | Access |
|--------|-------------|--------|
| `get_post(id, slug)` | Get a single product | Public |
| `get_posts(...)` | List products with filters | Public |
| `get_topic(id, slug)` | Get a topic/category | Public |
| `get_topics(...)` | List topics | Public |
| `get_collection(id, slug)` | Get a curated collection | Public |
| `get_collections(...)` | List collections | Public |
| `get_collection_posts(...)` | Products in a collection | Public |
| `get_post_comments(...)` | Comments on a product | Public (user info redacted) |
| `get_post_votes(...)` | Votes on a product | Public (user info redacted) |
| `get_comment(id)` | Get a single comment | Public (user info redacted) |
| `get_viewer()` | Current authenticated user | Your data only |
| `get_user(id, username)` | Get user profile | Your data only |
| `get_user_posts(...)` | Products made by user | Your data only |
| `get_user_voted_posts(...)` | Products user upvoted | Your data only |
| `get_user_followers(...)` | User's followers | Your data only |
| `get_user_following(...)` | Who user follows | Your data only |
| `follow_user(user_id)` | Follow a user | Requires write scope |
| `unfollow_user(user_id)` | Unfollow a user | Requires write scope |
| `graphql(query, variables)` | Run custom GraphQL | Varies |

## Pagination

All list methods return paginated results:

```python
# First page
page1 = client.get_posts(first=20)

# Next page
if page1.page_info.has_next_page:
    page2 = client.get_posts(first=20, after=page1.page_info.end_cursor)

# Iterate all items
for post in page1.nodes:
    print(post.name)
```

## Error Handling

```python
from producthunt_sdk import (
    ProductHuntError,
    AuthenticationError,
    RateLimitError,
    GraphQLError,
)

try:
    posts = client.get_posts()
except AuthenticationError:
    print("Check your API token")
except RateLimitError as e:
    print(f"Rate limited. Retry in {e.rate_limit_info.seconds_until_reset}s")
except GraphQLError as e:
    print(f"API error: {e.message}")
except ProductHuntError as e:
    print(f"Something went wrong: {e}")
```

## License

MIT
