Metadata-Version: 2.4
Name: pychive
Version: 1.1.0
Summary: A Python wrapper for the Pawchive (pawchive.pw) API.
Author: Pychive
License: MIT
Project-URL: Homepage, https://gitlab.com/forgiving/pychive
Project-URL: Repository, https://gitlab.com/forgiving/pychive
Keywords: pawchive,api,wrapper,archive,fanbox,patreon
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: responses>=0.23; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Dynamic: license-file

# Pychive

**Unofficial** Python wrapper for the [Pawchive](https://pawchive.pw) API.
This project is not affiliated with, endorsed by, or associated with Pawchive.

Pychive provides a clean, typed, object-oriented interface to Pawchive's REST
API — covering creators, posts, comments, announcements, fancards, post
flagging, favorites, file-hash search, and app version info.

## Documentation

Full documentation — installation, quick start, authentication, configuration,
API reference, models, exceptions, error handling, pagination, cookbook, and
changelog — lives in [`docs/index.md`](docs/index.md).

## Installation

### From PyPI

```bash
pip install pychive
```

### From source (local build)

```bash
git clone https://gitlab.com/forgiving/pychive.git
cd Pychive
pip install .
```

<details>
<summary>Optional: use a virtual environment</summary>

```bash
git clone https://gitlab.com/forgiving/pychive.git
cd Pychive

python3 -m venv .venv
source .venv/bin/activate      # Linux / macOS
# .venv\Scripts\activate       # Windows

pip install .
```

</details>

### Verify the install

```bash
python -c "import pychive; print(pychive.__version__)"
```

### (Optional) Install dev tooling

```bash
pip install -e ".[dev]"
```

This adds `pytest`, `responses`, `ruff`, and `mypy` for testing/linting.

---

## Quick start

```python
import os
from pychive import Pawchive, AuthenticationError, NotFoundError

# Log in with username / password — Pychive captures the session cookie.
# Or pass a session cookie directly: Pawchive("your_session_value")
# Or run anonymously for public endpoints: Pawchive()
client = Pawchive(
    username=os.environ["PAWCHIVE_USERNAME"],
    password=os.environ["PAWCHIVE_PASSWORD"],
)

# Public endpoints (no auth needed)
creators = client.creators.list_all()
for c in creators:
    print(c.name, c.service, c.url)

# Recent posts across all creators (paginated)
posts = client.posts.list_recent(limit=50, offset=0)
for p in posts:
    print(p.title, p.published_at)

# Posts from a specific creator
fanbox_posts = client.posts.list_from_creator("fanbox", "12345", limit=20)

# A specific post + its revisions and comments
post = client.posts.get("fanbox", "12345", "67890")
revisions = client.posts.list_revisions("fanbox", "12345", "67890")
comments = client.comments.list("fanbox", "12345", "67890")

# Creator profile / links / tags / announcements / fancards
profile = client.creators.get_profile("fanbox", "12345")
links = client.creators.get_links("fanbox", "12345")
tags = client.creators.get_tags("fanbox", "12345")
announcements = client.creators.get_announcements("fanbox", "12345")
fancards = client.creators.get_fancards("fanbox", "12345")  # fanbox only

# Authorized endpoints (require valid credentials)
try:
    favs = client.favorites.list()
    client.favorites.add_post("fanbox", "12345", "67890")
    client.favorites.add_creator("fanbox", "12345")
    client.flags.flag("fanbox", "12345", "67890")
    flag_status = client.flags.get_status("fanbox", "12345", "67890")
except AuthenticationError:
    print("Session is invalid or expired.")

# File search by hash + app version
result = client.search.lookup_hash("abcdef1234567890")
version = client.misc.app_version()

client.close()
```

### Authentication

Pychive supports three auth modes:

1. **Username / password** (recommended) — Pychive logs in to
   `pawchive.pw/account/login` and captures the `session` cookie:
   ```python
   client = Pawchive(username="your_username", password="your_password")
   ```
2. **Session cookie** — pass the `session` cookie value directly:
   ```python
   client = Pawchive("your_session_value")
   client = Pawchive("session=your_session_value")  # prefix optional
   ```
3. **Anonymous** — no credentials; public endpoints only:
   ```python
   client = Pawchive()
   ```

You can load credentials from environment variables:

```python
import os
from pychive import Pawchive

client = Pawchive(
    username=os.environ["PAWCHIVE_USERNAME"],
    password=os.environ["PAWCHIVE_PASSWORD"],
)
```

```bash
export PAWCHIVE_USERNAME="your_username"
export PAWCHIVE_PASSWORD="your_password"
```

---

## API surface

The `Pawchive` client exposes endpoint groups as properties:

| Property       | Class                 | Single-id methods                                                                              | Multi-id (`*_many`) methods                                                                          | Auth? |
| -------------- | --------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----- |
| `client.creators`  | `CreatorsEndpoint`  | `list_all()`, `get_profile(s,cid)`, `get_links(s,cid)`, `get_tags(s,cid)`, `get_announcements(s,cid)`, `get_fancards(s,cid)` | `get_profile_many(s,cids)`, `get_links_many(s,cids)`, `get_tags_many(s,cids)`, `get_announcements_many(s,cids)`, `get_fancards_many(cids)` | No (fancards: fanbox only) |
| `client.posts`     | `PostsEndpoint`     | `list_recent(l,o)`, `list_from_creator(s,cid,l,o)`, `get(s,cid,pid)`, `list_revisions(s,cid,pid)` | `list_from_creator_many(s,cids,l,o)`, `get_many(s,cid,pids)`, `list_revisions_many(s,cid,pids)`     | No |
| `client.comments`  | `CommentsEndpoint`  | `list(s,cid,pid)`                                                                              | `list_many(s,cid,pids)`                                                                              | No |
| `client.flags`     | `FlagsEndpoint`     | `flag(s,cid,pid)`, `get_status(s,cid,pid)`                                                     | `flag_many(s,cid,pids)`, `get_status_many(s,cid,pids)`                                               | Yes |
| `client.favorites` | `FavoritesEndpoint` | `list()`, `add_post(s,cid,pid)`, `remove_post(s,cid,pid)`, `add_creator(s,cid)`, `remove_creator(s,cid)` | `add_post_many(s,cid,pids)`, `remove_post_many(s,cid,pids)`, `add_creator_many(s,cids)`, `remove_creator_many(s,cids)` | Yes |
| `client.search`    | `SearchEndpoint`    | `lookup_hash(hash)`                                                                            | —                                                                                                    | No |
| `client.misc`      | `MiscEndpoint`      | `app_version()`                                                                                | —                                                                                                    | No |

> **Multi-id methods** accept a string or iterable of ids, return a `dict`
> keyed by id, and accept an optional `delay` keyword to throttle between
> requests. A bare string is treated as a single id (not character-split).

### Constructor options

```python
Pawchive(
    session=None,            # str | None: session cookie value
    *,
    username=None,           # str | None: Pawchive username
    password=None,           # str | None: Pawchive password
    login_url="https://pawchive.pw/account/login",
    base_url="https://pawchive.pw/api/v1",
    timeout=30,              # per-request timeout (seconds)
    max_retries=3,           # retries on 429 / 5xx
    backoff_base=0.5,        # exponential backoff base (seconds)
)
```

---

## Error handling

All errors inherit from `pychive.PawchiveError`. Catch the base class or a
specific subclass:

| Exception              | When raised                                    |
| ---------------------- | ---------------------------------------------- |
| `PawchiveError`        | Base class for all wrapper errors              |
| `AuthenticationError`  | HTTP 401 (invalid/missing session)             |
| `NotFoundError`        | HTTP 404                                       |
| `RateLimitError`       | HTTP 429 after retries exhausted               |
| `ClientError`          | Other HTTP 4xx                                 |
| `ServerError`          | HTTP 5xx after retries exhausted               |
| `ValidationError`      | Invalid arguments passed to a method           |
| `ConnectionError`      | Network/timeout failures                       |

Each HTTP exception carries `.status_code` and `.response` (the original
`requests.Response`). `RateLimitError` also exposes `.retry_after`.

```python
import os
from pychive import Pawchive, RateLimitError, AuthenticationError, NotFoundError

client = Pawchive(
    username=os.environ["PAWCHIVE_USERNAME"],
    password=os.environ["PAWCHIVE_PASSWORD"],
)
try:
    post = client.posts.get("fanbox", "12345", "67890")
except NotFoundError:
    print("Post not found.")
except AuthenticationError:
    print("Session expired.")
except RateLimitError as e:
    print(f"Rate limited; server suggested waiting {e.retry_after}s")
finally:
    client.close()
```

Retry behavior: the client automatically retries `429`, `502`, `503`, and
`504` responses up to `max_retries` times with exponential backoff. It honors
the `Retry-After` header when present (capped at 60s).

---

## Project layout

```
Pychive/
├── pyproject.toml          # packaging + tool config
├── README.md
├── AGENTS.md               # compact guide for AI coding sessions
├── docs/                   # full documentation (Markdown)
│   ├── index.md            # entry point + table of contents
│   ├── installation.md
│   ├── quickstart.md
│   ├── authentication.md
│   ├── configuration.md
│   ├── api-reference.md    # every endpoint, method, return type
│   ├── models.md           # every dataclass and its fields
│   ├── exceptions.md
│   ├── error-handling.md
│   ├── pagination.md
│   ├── cookbook.md         # complete examples
│   └── changelog.md
├── tests/                  # pytest test suite (responses for HTTP mocking)
│   ├── conftest.py         # shared fixtures (client with max_retries=0)
│   ├── test_session_parsing.py
│   ├── test_models.py
│   ├── test_validation.py
│   ├── test_endpoints.py
│   ├── test_error_handling.py
│   └── test_client.py
└── src/
    └── pychive/
        ├── __init__.py     # public exports (Pawchive, models, exceptions)
        ├── client.py       # Pawchive — main client class
        ├── http.py         # HttpClient — transport, auth, retry/backoff
        ├── exceptions.py   # exception hierarchy
        ├── models.py       # frozen dataclasses for API responses
        ├── py.typed        # PEP 561 marker (typed package)
        └── endpoints/
            ├── __init__.py
            ├── base.py        # BaseEndpoint
            ├── creators.py    # list_all, profile, links, tags, announcements, fancards
            ├── posts.py       # recent, creator posts, specific post, revisions
            ├── comments.py    # post comments
            ├── flags.py       # flag / flag status
            ├── favorites.py   # list / add / remove posts & creators
            ├── search.py      # hash lookup
            └── misc.py        # app version
```

---

## Development

```bash
pip install -e ".[dev]"

# lint
ruff check src
# type-check
mypy src
# tests
pytest
```

## License

MIT
