Metadata-Version: 2.4
Name: aisha-ai
Version: 1.0.0
Summary: Python SDK for Aisha AI text-to-speech and speech-to-text
Project-URL: Homepage, https://aisha.group/en/api-documentation
Project-URL: Repository, https://github.com/aisha-group/sdk
Project-URL: Issues, https://github.com/aisha-group/sdk/issues
Author-email: Aisha AI <info@aisha.group>
License-Expression: MIT
License-File: LICENSE
Keywords: ai,aisha,aisha ai,aisha-ai,speech,speech-to-text,stt,text-to-speech,transcription,tts,uzbek,voice
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 :: Only
Classifier: Programming Language :: Python :: 3.8
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: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.8
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy<1.15,>=1.10; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

# Aisha AI Python SDK

`aisha-ai` is the Python SDK for the [Aisha AI](https://aisha.group) API.

Use it when you want to add text-to-speech or speech-to-text to a Python app,
backend service, script, or automation.

```python
from aisha_ai import AishaClient

client = AishaClient(api_key="your-api-key")
result = client.tts(transcript="Salom dunyo")

print(result["audioUrl"])
```

## Contents

- [Features](#features)
- [Requirements](#requirements)
- [Install](#install)
- [Quick Start](#quick-start)
- [API Versions](#api-versions)
- [Server Integration Flow](#server-integration-flow)
- [Client Setup](#client-setup)
- [Text-to-Speech](#text-to-speech)
- [Speech-to-Text](#speech-to-text)
- [History](#history)
- [Parameter Reference](#parameter-reference)
- [SDK Constants](#sdk-constants)
- [Error Handling](#error-handling)
- [Optional CLI](#optional-cli)
- [Development](#development)
- [Continuous Integration](#continuous-integration)
- [Publishing](#publishing)
- [Official API Docs](#official-api-docs)

## Features

- Text-to-speech (TTS)
- Speech-to-text (STT)
- TTS and STT history
- Sync TTS requests
- Async TTS requests with webhook callbacks
- Clean Python exceptions for API and network errors
- No runtime dependencies outside the Python standard library

## Requirements

- Python 3.8 or newer
- An Aisha AI API key

Create an API key at [space.aisha.group](https://space.aisha.group).

## Install

```bash
pip install aisha-ai
```

For local development from this repository:

```bash
python -m pip install -e .
```

## Quick Start

Set your API key:

```bash
export AISHA_API_KEY="your-api-key"
```

Use the SDK:

```python
import os

from aisha_ai import AishaClient

client = AishaClient(api_key=os.environ["AISHA_API_KEY"])

result = client.tts(transcript="Salom dunyo")
print(result["audioUrl"])
```

## API Versions

This SDK currently targets these Aisha API versions:

| Feature | API version | SDK methods |
| --- | --- | --- |
| TTS sync and async | API v1 | `tts()`, `tts_status()`, `tts_history()` |
| STT short-audio sync | API v1 | `stt()`, `stt_history()` |

The Aisha API also documents STT API v2 for long-audio async transcription.
The client defaults to `api_version="v1"`, but the version is configurable:

```python
client = AishaClient(api_key="your-api-key", api_version="v2")
```

You can also pass `api_version=2`; the SDK normalizes it to `"v2"`. Existing
SDK methods then call `/api/v2/...` paths. Only switch versions for endpoints
that are supported by the Aisha API version you are integrating with.

## Server Integration Flow

For server-to-server traffic, Aisha uses the `X-Api-Key` header. The SDK sends
this header for every request:

```http
X-Api-Key: your-api-key
```

Recommended backend integration flow:

1. Create an API key in Space.
2. Send a small test TTS or STT request.
3. Confirm the response shape your app needs.
4. For async TTS, store the returned `id` or `task_id`.
5. Use status and history endpoints to cover the full workflow.

With the SDK, that flow looks like this:

```python
from aisha_ai import AishaClient

client = AishaClient(api_key="your-api-key")

queued = client.tts(
    transcript="Webhook orqali qaytadigan sinov matni.",
    webhook_url="https://example.com/webhooks/tts",
)

status = client.tts_status(queued["id"])
history = client.tts_history(page=1, limit=10)
```

## Client Setup

Create a client:

```python
from aisha_ai import AishaClient

client = AishaClient(api_key="your-api-key")
```

Set the response language for API messages:

```python
client = AishaClient(api_key="your-api-key", language="uz")
```

Use a custom API base URL:

```python
client = AishaClient(
    api_key="your-api-key",
    base_url="https://back.aisha.group",
)
```

Use a different API version:

```python
client = AishaClient(
    api_key="your-api-key",
    api_version="v2",
)
```

Change request timeout:

```python
client = AishaClient(api_key="your-api-key", timeout=60)
```

## Text-to-Speech

### Basic TTS

By default, TTS returns the generated audio URL. It does not download the file:

```python
result = client.tts(transcript="Assalomu alaykum")

print(result["audioUrl"])
```

### Save TTS Audio to a File

Pass `output_path` when you want the SDK to download the generated audio for
you:

```python
result = client.tts(
    transcript="Assalomu alaykum",
    output_path="outputs/assalomu-alaykum.wav",
)

print(result["audioUrl"])
print(result["output_path"])
```

The SDK creates parent folders when needed. If you do not pass `output_path`,
the SDK only returns the URL and does not write anything to disk.
When downloading, the SDK sends the same API key header plus a normal SDK
`User-Agent`, so protected audio URLs can be fetched from backend services and
CI jobs.

You can also download an existing audio URL later:

```python
client.download_audio(
    url=result["audioUrl"],
    output_path="outputs/from-url.wav",
)
```

### Uzbek TTS Options

For Uzbek TTS, you can pass `model`, `mood`, and `speed`:

```python
result = client.tts(
    transcript="Assalomu alaykum",
    language="uz",
    model="Gulnoza",
    mood="Happy",
    speed=1.0,
)

print(result["audioUrl"])
```

Known mood values:

- `Neutral`
- `Cheerful`
- `Happy`
- `Sad`

Default model:

- `Gulnoza`

Speed:

- `0` uses the API default speed
- `0.5` is slower
- `1.0` is normal speed
- `2.0` is faster

The SDK sends `model`, `mood`, and `speed` only when `language="uz"`.

### English or Russian TTS

```python
result = client.tts(
    transcript="Hello",
    language="en",
)
```

```python
result = client.tts(
    transcript="Privet",
    language="ru",
)
```

### Async TTS with Webhook

Pass `webhook_url` when you want the API to process TTS in the background:

```python
queued = client.tts(
    transcript="Salom dunyo",
    webhook_url="https://example.com/aisha-webhook",
)

print(queued["id"])
print(queued["status"])
```

Check the task later:

```python
status = client.tts_status(queued["id"])
print(status)
```

Use `output_path` only for sync TTS. Async TTS returns task information first,
so there is no completed audio file to download immediately.

## Speech-to-Text

### Transcribe a File Path

```python
result = client.stt(file="meeting.wav")
print(result["transcript"])
```

### Transcribe Bytes

```python
with open("meeting.wav", "rb") as audio:
    data = audio.read()

result = client.stt(file=data, filename="meeting.wav")
print(result["transcript"])
```

### Enable Diarization

Use diarization when you want speaker separation:

```python
result = client.stt(
    file="meeting.wav",
    diarization=True,
)
```

When diarization is enabled, the API expects audio to be at least 15 seconds
long.

## History

List past TTS requests:

```python
tts_history = client.tts_history(page=1, limit=10)
```

List past STT requests:

```python
stt_history = client.stt_history(page=1, limit=10)
```

`page` and `limit` must be positive integers.

## Parameter Reference

### `AishaClient(...)`

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `api_key` | `str` | required | API key from `space.aisha.group`. |
| `base_url` | `str` | `https://back.aisha.group` | API base URL. |
| `api_version` | `str \| int` | `v1` | API version used in endpoint paths. |
| `language` | `str \| None` | `None` | API message language: `uz`, `en`, or `ru`. |
| `timeout` | `float` | `120.0` | Request timeout in seconds. |

### `client.tts(...)`

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `transcript` | `str` | required | Text to turn into speech. Max 1000 characters. |
| `language` | `str` | `uz` | Voice language: `uz`, `en`, or `ru`. |
| `model` | `str` | `Gulnoza` | Uzbek TTS voice model. Sent only for `language="uz"`. |
| `mood` | `str` | `Neutral` | Uzbek TTS mood. See [SDK Constants](#sdk-constants). |
| `speed` | `float \| str` | `1.0` | Uzbek TTS speed. Use `0`, or a value from `0.5` to `2.0`. |
| `webhook_url` | `str \| None` | `None` | Webhook URL for async TTS. |
| `output_path` | `str \| path \| None` | `None` | Local path where audio should be saved. |

### `client.tts_status(...)`

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `int \| str` | required | Async TTS task ID. |

### `client.download_audio(...)`

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `url` | `str` | required | Full audio URL to download. |
| `output_path` | `str` or path | required | Local path where audio should be saved. |

### `client.stt(...)`

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `file` | `str`, `bytes`, path, or binary file object | required | Audio file to transcribe. |
| `language` | `str` | `uz` | Audio language: `uz`, `en`, or `ru`. |
| `diarization` | `bool` | `False` | Enables speaker diarization. |
| `filename` | `str \| None` | `None` | File name used in upload. Useful when passing raw bytes. |

### `client.tts_history(...)` and `client.stt_history(...)`

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `page` | `int` | `1` | Page number. Must be positive. |
| `limit` | `int` | `10` | Rows per page. Must be positive. |

## SDK Constants

You can import common values from the package:

```python
from aisha_ai import (
    DEFAULT_API_VERSION,
    DEFAULT_TTS_MODEL,
    DEFAULT_TTS_MOOD,
    DEFAULT_TTS_SPEED,
    MAX_TTS_TRANSCRIPT_LENGTH,
    STT_API_VERSION,
    SUPPORTED_AUDIO_FORMATS,
    SUPPORTED_LANGUAGES,
    TTS_API_VERSION,
    TTS_MOODS,
)

print(SUPPORTED_LANGUAGES)
print(TTS_MOODS)
print(DEFAULT_API_VERSION)
print(TTS_API_VERSION)
```

Current values:

| Name | Value |
| --- | --- |
| `DEFAULT_API_VERSION` | `"v1"` |
| `SUPPORTED_LANGUAGES` | `{"uz", "en", "ru"}` |
| `TTS_API_VERSION` | `"v1"` |
| `STT_API_VERSION` | `"v1"` |
| `SUPPORTED_AUDIO_FORMATS` | `("mp3", "wav", "ogg", "m4a")` |
| `TTS_MOODS` | `("Neutral", "Cheerful", "Happy", "Sad")` |
| `DEFAULT_TTS_MODEL` | `"Gulnoza"` |
| `DEFAULT_TTS_MOOD` | `"Neutral"` |
| `DEFAULT_TTS_SPEED` | `1.0` |
| `MIN_TTS_SPEED` | `0.5` |
| `MAX_TTS_SPEED` | `2.0` |
| `MAX_TTS_TRANSCRIPT_LENGTH` | `1000` |

## Error Handling

The SDK raises `AishaApiError` when the API returns an error response:

```python
from aisha_ai import AishaApiError, AishaClient

client = AishaClient(api_key="your-api-key")

try:
    result = client.tts(transcript="Salom")
except AishaApiError as error:
    print(error.status)
    print(error.body)
    print(error.url)
```

Network failures raise `AishaConnectionError`. It is a subclass of
`AishaApiError`, so one `except AishaApiError` block can handle both API and
network failures.

For connection errors:

- `error.status` is `0`
- `error.body` is `None`

The SDK also raises normal Python validation errors before making a request:

- `TypeError` for missing or wrong input types
- `ValueError` for invalid values, such as unsupported language or invalid page

## Optional CLI

The package also installs an `aisha-ai` command. It is useful for quick manual
tests, demos, or simple scripts. For applications, prefer importing
`AishaClient` in Python code.

Set your API key:

```bash
export AISHA_API_KEY="your-api-key"
```

Generate speech:

```bash
aisha-ai tts "Salom dunyo" --out salom.wav
```

Generate Uzbek TTS with options:

```bash
aisha-ai tts "Assalomu alaykum" --model Gulnoza --mood Happy --speed 1.2
```

Run async TTS:

```bash
aisha-ai tts "Salom dunyo" --webhook https://example.com/aisha-webhook --json
```

Transcribe audio:

```bash
aisha-ai stt meeting.wav
```

Enable diarization:

```bash
aisha-ai stt meeting.wav --diarization
```

View history:

```bash
aisha-ai history tts --page 1 --limit 10
aisha-ai history stt --page 1 --limit 10
```

Print raw JSON:

```bash
aisha-ai history tts --json
```

Use a key without setting an environment variable:

```bash
aisha-ai tts "Salom" --api-key "your-api-key"
```

## Development

Run the test suite:

```bash
python -m unittest discover -s tests -v
```

The tests use an in-process mock API server. They do not call the real Aisha AI
API and do not need a real API key.

Install build tools:

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

## Continuous Integration

Pull requests and pushes to `main` run GitHub Actions CI.

The test job checks:

- package installation
- Python syntax compilation
- unit tests with the local mock API server
- Python 3.8 through Python 3.13

The quality job checks:

- lint with Ruff
- formatting with Ruff format
- package type checking with mypy

The package job checks:

- wheel build
- source distribution build
- package metadata and README rendering with `twine check`

The real API smoke test job runs after quality checks, unit tests, and package
build. It uses the repository secret `AISHA_API_TEST_KEY` and checks:

- a few short TTS requests with different Uzbek options
- downloading one generated audio file with `output_path`
- TTS history response shape
- a few edge cases

If `AISHA_API_TEST_KEY` is missing, the real API tests are skipped.

Publishing is separate from normal merges:

- Pull requests test the code before merge.
- Pushes to `main` test and build the package.
- Version tags like `v1.0.1` publish to PyPI after CI passes.
- The PyPI publish job uses the GitHub secret `PYPI_API_TOKEN`.
- The publish job uses the protected GitHub environment named `pypi`.

## Publishing

This repository is set up for CI-based publishing. You should not upload files
from your laptop for normal releases.

### One-time PyPI setup

Create a PyPI API token:

1. Log in to PyPI.
2. Open Account settings -> API tokens.
3. Create a token named `aisha-python-github-actions`.
4. For the first publish, use an account-scoped token because the `aisha-ai`
   project does not exist yet.
5. Add the token to this repository as the GitHub Actions secret
   `PYPI_API_TOKEN`.

After the first successful publish, replace the account-scoped token with a
project-scoped token for `aisha-ai`.

Then configure the GitHub environment:

1. Open the repository on GitHub.
2. Go to Settings -> Environments.
3. Create an environment named `pypi`.
4. Add required reviewers if you want a manual approval before publishing.
5. Allow deployments only from tags that match `v*`.

### Release flow

Before releasing:

1. Update `version` in `pyproject.toml`.
2. Update `__version__` in `src/aisha_ai/client.py`.
3. Open a pull request.
4. Wait for CI to pass.
5. Merge to `main`.
6. Create and push a matching version tag.

Example:

```bash
git checkout main
git pull
git tag v1.0.1
git push origin v1.0.1
```

The tag starts CI again. If tests and package checks pass, the publish job
uploads the package to PyPI.

### Manual local build

Build the package:

```bash
python -m build
```

Check the package:

```bash
python -m twine check dist/*
```

Upload to TestPyPI manually if needed:

```bash
python -m twine upload --repository testpypi dist/*
```

Upload to PyPI manually only if CI publishing is unavailable:

```bash
python -m twine upload dist/*
```

## Links

- Aisha AI: https://aisha.group
- API documentation: https://aisha.group/en/api-documentation
- API keys: https://space.aisha.group

## Official API Docs

- TTS API: https://aisha.group/en/api-documentation/text-to-speech
- STT API: https://aisha.group/en/api-documentation/speech-to-text
