Metadata-Version: 2.4
Name: ddapi
Version: 2.0.1
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
License-File: LICENSE
Summary: Async Python bindings for the DDNet and DDStats APIs (Teeworlds)
Keywords: ddnet,ddstats,teeworlds,api,ddapi
Author-email: ByFox213 <byfox213@gmail.com>
License-Expression: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/ByFox213/ddapi-rs

ddapi-rs
=======

A small async Rust library for working with the DDNet and DDStats public APIs.

- Crates.io: https://crates.io/crates/ddapi-rs
- Docs.rs: https://docs.rs/ddapi-rs
- PyPI: https://pypi.org/project/ddapi

Features
--------

- `ddnet` (default) - DDNet API (`ddnet.org`)
- `ddstats` - DDStats API (`ddstats.tw`)
- `cache` - in-memory cache for responses (uses `moka`)
- `full` - enables `ddnet`, `ddstats`, `cache`

Installation
------------

Default (DDNet only):

```bash
cargo add ddapi-rs
```

Enable DDStats too:

```bash
cargo add ddapi-rs -F ddstats
```

Enable caching:

```bash
cargo add ddapi-rs -F cache
```

Everything:

```bash
cargo add ddapi-rs -F full
```

Usage
-----

The crate is async and uses `tokio`.

DDNet example (enabled by default):

```rust
use ddapi_rs::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let api = DDApi::new();

    // ddnet: player points
    let player = api.player("nameless tee").await?;
    println!("{}: {}", player.player, player.points.points.unwrap_or(0));
    Ok(())
}
```

DDStats example (requires `-F ddstats`):

```rust
use ddapi_rs::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let api = DDApi::new();

    // ddstats: profile info
    let profile = api.profile("ByFox").await?;
    println!("{} ({})", profile.name, profile.clan.unwrap_or_default());
    Ok(())
}
```

Optional: only use one API
--------------------------

If you do not want a combined client, you can use `DDnetClient` / `DDstatsClient`.

```rust
use ddapi_rs::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let ddnet = DDnetClient::new();
    let p = ddnet.player("nameless tee").await?;
    println!("{}: {}", p.player, p.points.points.unwrap_or(0));
    Ok(())
}
```

Caching (feature `cache`)
-------------------------

```rust
use ddapi_rs::prelude::*;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<()> {
    let mut api = DDApi::new();
    api.set_cache(1000, Duration::from_secs(60 * 5));

    // Will be cached based on URL + TTL
    let _ = api.status().await?;
    Ok(())
}
```

Custom reqwest client
---------------------

```rust
use ddapi_rs::prelude::*;
use reqwest::Client;

fn main() {
    let client = Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .build()
        .unwrap();

    let _api = DDApi::new_with_client(client);
}
```

Python (PyPI package `ddapi`)
=============================

```bash
pip install ddapi
```

The Python package mirrors the Rust API. All methods are async and return
typed dataclasses (`ddapi.ddnet.*` / `ddapi.ddstats.*`) instead of raw dicts.
Fields that may be absent in the API response are `None`.

```python
import asyncio

import ddapi


async def main():
    api = ddapi.DDApi()

    # ddnet: player points
    player = await api.player("nameless tee")
    print(player.player, player.points.points)  # fields, not dict keys

    # optional data is None - no KeyError
    if player.team_rank is not None:
        print(player.team_rank.rank)

    # ddstats: profile info
    stats = ddapi.DDstatsClient()
    profile = await stats.profile("ByFox")
    print(profile.name, profile.clan)


asyncio.run(main())
```

Exposed API:

- `DDApi` - combined client (DDNet methods take precedence)
- `DDnetClient` - DDNet API only: `master`, `custom_master`, `skins`,
  `player`, `query`, `query_map`, `query_mapper`, `map`, `releases_map`,
  `status`, `latest_finish`, `latest_finish_with_latest`
- `DDstatsClient` - DDStats API only: `player`, `map`, `maps`, `profile`,
  `teero`
- `ddapi.ddnet` / `ddapi.ddstats` - dataclass models (e.g. `ddapi.ddnet.Player`)
- `DDError` - exception raised on API errors
- `__version__` - matches the Rust crate version

Notes:

- `custom_master` takes a master server index (`1`-`4`).
- Timestamps arrive as `datetime` (UTC); dates as `date`.
- Optional per-client in-memory cache:

```python
api = ddapi.DDApi()
api.set_cache(10_000, 600)  # capacity, ttl in seconds
```

