Metadata-Version: 2.4
Name: elaunira-r2index
Version: 0.5.0
Summary: Python library for uploading files to R2 and registering them with the r2index API
Project-URL: Homepage, https://github.com/elaunira/elaunira-r2-index
Project-URL: Repository, https://github.com/elaunira/elaunira-r2-index
Author: Elaunira
License-Expression: MIT
Keywords: cloudflare,index,r2,storage,upload
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: aioboto3>=13.0.0
Requires-Dist: boto3>=1.35.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.10.0
Provides-Extra: dev
Requires-Dist: boto3-stubs[s3]>=1.35.0; extra == 'dev'
Requires-Dist: build>=1.2.0; extra == 'dev'
Requires-Dist: mypy>=1.15.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.25.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.35.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.9.0; extra == 'dev'
Requires-Dist: twine>=6.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# elaunira-r2index

Python library for uploading and downloading files to/from Cloudflare R2 with the r2index API.

## Installation

```bash
pip install elaunira-r2index
```

## Usage

### Sync Client

```python
from elaunira.r2index import R2IndexClient

client = R2IndexClient(
    index_api_url="https://r2index.example.com",
    index_api_token="your-bearer-token",
    r2_access_key_id="your-r2-access-key-id",
    r2_secret_access_key="your-r2-secret-access-key",
    r2_endpoint_url="https://your-account-id.r2.cloudflarestorage.com",
)

# Upload and register a file
record = client.upload(
    bucket="my-bucket",
    local_path="./myfile.zip",
    category="software",
    entity="myapp",
    extension="zip",
    media_type="application/zip",
    remote_path="/releases/myapp",
    remote_filename="myapp.zip",
    remote_version="v1",
    tags=["release", "stable"],
)

# Download a file and record the download
# IP address is auto-detected, user agent defaults to "elaunira-r2index/<version>"
path, record = client.download(
    bucket="my-bucket",
    object_id="/releases/myapp/v1/myapp.zip",
    destination="./downloads/myfile.zip",
)
```

### Async Client

```python
from elaunira.r2index import AsyncR2IndexClient

async with AsyncR2IndexClient(
    index_api_url="https://r2index.example.com",
    index_api_token="your-bearer-token",
    r2_access_key_id="your-r2-access-key-id",
    r2_secret_access_key="your-r2-secret-access-key",
    r2_endpoint_url="https://your-account-id.r2.cloudflarestorage.com",
) as client:
    # Upload
    record = await client.upload(
        bucket="my-bucket",
        local_path="./myfile.zip",
        category="software",
        entity="myapp",
        extension="zip",
        media_type="application/zip",
        remote_path="/releases/myapp",
        remote_filename="myapp.zip",
        remote_version="v1",
        tags=["release", "stable"],
    )

    # Download
    path, record = await client.download(
        bucket="my-bucket",
        object_id="/releases/myapp/v1/myapp.zip",
        destination="./downloads/myfile.zip",
    )
```

### Transfer Configuration

Control multipart transfer settings with `R2TransferConfig`:

```python
from elaunira.r2index import R2IndexClient, R2TransferConfig

client = R2IndexClient(
    index_api_url="https://r2index.example.com",
    index_api_token="your-bearer-token",
    r2_access_key_id="your-r2-access-key-id",
    r2_secret_access_key="your-r2-secret-access-key",
    r2_endpoint_url="https://your-account-id.r2.cloudflarestorage.com",
)

# Custom transfer settings
transfer_config = R2TransferConfig(
    multipart_threshold=100 * 1024 * 1024,  # 100MB (default)
    multipart_chunksize=32 * 1024 * 1024,   # 32MB chunks
    max_concurrency=64,                      # 64 parallel threads
    use_threads=True,                        # Enable threading (default)
)

path, record = client.download(
    bucket="my-bucket",
    object_id="/data/files/v2/largefile.zip",
    destination="./downloads/largefile.zip",
    transfer_config=transfer_config,
)
```

Default `max_concurrency` is 2x the number of CPU cores (minimum 4).

### Progress Tracking

```python
def on_progress(bytes_transferred: int) -> None:
    print(f"Downloaded: {bytes_transferred / 1024 / 1024:.1f} MB")

path, record = client.download(
    bucket="my-bucket",
    object_id="/releases/myapp/v1/myapp.zip",
    destination="./downloads/myfile.zip",
    progress_callback=on_progress,
)
```

### Deleting Files

```python
# Delete from R2 storage
client.delete_from_r2(
    bucket="my-bucket",
    object_id="/releases/myapp/v1/myapp.zip",
)

# Delete from index (metadata only)
client.delete(file_id)

# Or delete by remote tuple
client.delete_by_tuple(RemoteTuple(
    bucket="my-bucket",
    remote_path="/releases/myapp",
    remote_filename="myapp.zip",
    remote_version="v1",
))
```

## License

MIT
