Metadata-Version: 2.4
Name: quran-foundation-api
Version: 0.3.1
Summary: Official Python SDK for Quran Foundation APIs.
Project-URL: Homepage, https://api-docs.quran.foundation/docs/sdk/python/
Project-URL: Documentation, https://api-docs.quran.foundation/docs/sdk/python/
Project-URL: Support, https://api-docs.quran.foundation/request-access/
Author-email: Quran Foundation <developers@quran.foundation>
License-Expression: MIT
License-File: LICENSE
Keywords: api,oauth2,quran,quran-foundation,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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 :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.27
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: twine>=6.0; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.115; extra == 'fastapi'
Requires-Dist: uvicorn>=0.30; extra == 'fastapi'
Description-Content-Type: text/markdown

# quran-foundation-api

Official Python SDK for Quran Foundation APIs.

```bash
pip install quran-foundation-api
```

```python
from quran_foundation import QuranClient

client = QuranClient(client_id="YOUR_CLIENT_ID", access_token="ACCESS_TOKEN")
chapters = client.list_chapters()
print(chapters)
```

## Content and search

```python
from quran_foundation import QuranClient

client = QuranClient(client_id="YOUR_CLIENT_ID", access_token="ACCESS_TOKEN")

chapter = client.get_chapter(1)
verses = client.get_verses_by_range("1:1", "1:7", translations="131")
translations = client.list_translations()
results = client.search("mercy", mode="advanced", params={"size": 10})
```

### Content resource sync

Chapter recitations, Mushafs, word-by-word translations, and word-by-word
transliterations can be included in content sync and fetched as resource snapshots:

```python
changes = client.sync_resources(
    params={
        "bootstrap": True,
        "resources": (
            "chapter_recitations:159;mushafs:1;word_by_word_translations:85;"
            "word_by_word_transliterations:60"
        ),
    }
)
chapter_recitation_snapshot = client.get_chapter_recitation_snapshot(159)
snapshot = client.get_word_by_word_translation_snapshot(85)
transliteration_snapshot = client.get_word_by_word_transliteration_snapshot(60)
mushaf_snapshot = client.get_mushaf_snapshot(1)
```

Chapter-recitation snapshots use the audio recitation ID and contain its chapter
audio files. Mushaf snapshots contain the layout metadata, pages, publicly
distributable font assets, and words needed for offline use. The API also exposes
approved, shareable word-translation and word-transliteration resources. A
word-by-word transliteration snapshot record is available as the exported
`WordByWordTransliterationSnapshotRecord` type and contains `id`,
`resource_content_id`, `resource_id`, `word_id`, `language_id`, `language_name`,
`text`, and `updated_at`. Incremental row events for these resources use
`record_type="word_transliteration"`.

For endpoints that do not yet have a dedicated helper, use the service request helpers:

```python
client.content_request("/verses/by_page/1", params={"translations": "131"})
client.search_request("/api/v1/search", params={"query": "mercy", "mode": "quick"})
client.user_request("/bookmarks", params={"page": 1})
```

## Analytics Events

Analytics submission is server-side and requires a client-credentials access
token with the `analytics.events.write` scope. Keep the client secret and token
in server-side environment variables.

```python
import os
from datetime import datetime, timezone

from quran_foundation import AnalyticsEvent, QuranClient
from quran_foundation.oauth import client_credentials

token = client_credentials(
    client_id=os.environ["QURAN_CLIENT_ID"],
    client_secret=os.environ["QURAN_CLIENT_SECRET"],
    scope="analytics.events.write",
)
client = QuranClient(
    client_id=os.environ["QURAN_CLIENT_ID"],
    access_token=token["access_token"],
)

result = client.submit_analytics_events(
    [
        AnalyticsEvent(
            event_id="stable-event-id-1",
            name="quran.reader.verse_viewed",
            version=1,
            occurred_at=datetime.now(timezone.utc),
            user_id="QURAN_FOUNDATION_USER_ID",
            session_id="session-123",
            properties={"verse_key": "2:255", "surface": "reader"},
        ),
        AnalyticsEvent(
            event_id="stable-event-id-2",
            name="quran.app.started",
            version=1,
            occurred_at=datetime.now(timezone.utc),
            anonymous_id="anonymous-123",
        ),
    ]
)
```

A successful response accepts the complete batch. Retry a failed batch with
the same event IDs so downstream processing can identify duplicates. Use a
Quran Foundation OAuth user ID for `user_id`; omit it for guests or unknown
users.

## User APIs

Use signed-in User API helpers with a user access token. Keep the token server-side.

```python
profile = client.get_profile()
bookmarks = client.list_bookmarks()
client.create_bookmark({"verse_key": "2:255", "mushaf_id": 1})
client.update_preference({"key": "theme", "value": "dark"})
```

## OAuth2 helpers

Use OAuth helpers on the server side. Never expose `client_secret`, access tokens, or refresh tokens to browser code.

```python
from quran_foundation.oauth import build_authorization_url, create_pkce_pair, exchange_code

verifier, challenge = create_pkce_pair()
authorize_url = build_authorization_url(
    client_id="YOUR_CLIENT_ID",
    redirect_uri="https://your-app.com/callback",
    scope="openid offline_access user bookmark",
    state="random-state",
    code_challenge=challenge,
)

tokens = exchange_code(
    client_id="YOUR_CLIENT_ID",
    client_secret="YOUR_CLIENT_SECRET",
    code="CODE_FROM_CALLBACK",
    code_verifier=verifier,
    redirect_uri="https://your-app.com/callback",
)
```

## Development

```bash
python -m pip install -e ".[dev]"
ruff check .
pytest
python -m build
twine check dist/*
```

## Contributing and releases

Public API changes require tests, documentation, a changelog entry, and a
version bump. Merging to `main` runs CI but does not publish the package. See
[`CONTRIBUTING.md`](CONTRIBUTING.md) for the versioning and release process.
