Metadata-Version: 2.4
Name: bunnynet
Version: 0.4.0
Summary: A client for the bunny.net APIs
License: MIT
License-File: LICENSE
Keywords: bunny,bunny.net,cdn
Author: Dale Myers
Author-email: dale@myers.io
Requires-Python: >=3.10,<4.0
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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 :: Software Development
Classifier: Topic :: Utilities
Requires-Dist: deserialize (>=2.3.0,<3.0.0)
Requires-Dist: requests (>=2.34.2,<3.0.0)
Requires-Dist: tenacity (>=9.1.4,<10.0.0)
Project-URL: Homepage, https://github.com/dalemyers/bunnynet
Project-URL: Repository, https://github.com/dalemyers/bunnynet
Description-Content-Type: text/markdown

# bunnynet

A Python client for the [bunny.net](https://bunny.net) APIs.

## Installation

```bash
pip install bunnynet
```

## Usage

Create a client with your bunny.net account API token:

```python
import bunnynet

client = bunnynet.BunnyClient("your-api-token")
```

### Storage zones

```python
# Iterate over every storage zone
for zone in client.storage_zones.get_all():
    print(zone.name)

# Look one up by id or name (returns None if it doesn't exist)
zone = client.storage_zones.get_by_name("my-zone")

# Upload, read and delete files
client.storage_zones.upload_text_file(
    storage_zone=zone,
    contents="Hello World",
    path="some/path",
    file_name="hello.txt",
)
text = client.storage_zones.get_text_file(storage_zone=zone, path="some/path", file_name="hello.txt")
client.storage_zones.delete_file(storage_zone=zone, path="some/path", file_name="hello.txt")
```

### Pull zones

```python
for zone in client.pull_zones.get_all():
    print(zone.name)
```

#### Provisioning a pull zone

The pull zone client can also create and configure zones:

```python
pull_zones = client.get_pull_zone_client()

zone = pull_zones.get_by_name("my-zone") or pull_zones.create(
    name="my-zone",
    origin_url="https://origin.example.com",
)

# Apply settings using bunny.net's PascalCase field names. -1 on
# CacheControlMaxAgeOverride tells bunny.net to respect the origin's
# Cache-Control headers.
pull_zones.update(zone.identifier, {
    "IgnoreQueryStrings": False,
    "EnableQueryStringOrdering": True,
    "CacheControlMaxAgeOverride": -1,
})

# Attach a custom hostname, issue a free Let's Encrypt certificate and force
# HTTPS. The hostname's CNAME must resolve to <zone>.b-cdn.net before the
# certificate can be issued.
pull_zones.add_hostname(zone.identifier, "cdn.example.com")
pull_zones.load_free_certificate("cdn.example.com")
pull_zones.set_force_ssl(zone.identifier, "cdn.example.com", force=True)
```

### Signed URLs

```python
import datetime

signed = client.sign_url(
    "https://example.b-cdn.net/foo/bar/file.png",
    key="your-token-authentication-key",
    expiration=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=1),
)
```

### Purging the cache

```python
client.purge_url("https://example.b-cdn.net/foo/bar/file.png")
```

## Development

Install dependencies and run the tests with [Poetry](https://python-poetry.org):

```bash
poetry install
poetry run pytest
```

The integration tests in `tests/test_bunnynet.py` require a `BUNNY_TOKEN`
environment variable and are skipped when it is not set. The offline unit tests
in `tests/test_unit.py` always run.

## License

MIT

