ollang-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ollang_sdk-0.1.0/PKG-INFO +133 -0
- ollang_sdk-0.1.0/README.md +109 -0
- ollang_sdk-0.1.0/pyproject.toml +44 -0
- ollang_sdk-0.1.0/setup.cfg +4 -0
- ollang_sdk-0.1.0/src/ollang/__init__.py +59 -0
- ollang_sdk-0.1.0/src/ollang/_client.py +68 -0
- ollang_sdk-0.1.0/src/ollang/_exceptions.py +20 -0
- ollang_sdk-0.1.0/src/ollang/resources/__init__.py +7 -0
- ollang_sdk-0.1.0/src/ollang/resources/custom_instructions.py +41 -0
- ollang_sdk-0.1.0/src/ollang/resources/orders.py +119 -0
- ollang_sdk-0.1.0/src/ollang/resources/projects.py +35 -0
- ollang_sdk-0.1.0/src/ollang/resources/revisions.py +34 -0
- ollang_sdk-0.1.0/src/ollang/resources/uploads.py +65 -0
- ollang_sdk-0.1.0/src/ollang_sdk.egg-info/PKG-INFO +133 -0
- ollang_sdk-0.1.0/src/ollang_sdk.egg-info/SOURCES.txt +17 -0
- ollang_sdk-0.1.0/src/ollang_sdk.egg-info/dependency_links.txt +1 -0
- ollang_sdk-0.1.0/src/ollang_sdk.egg-info/requires.txt +1 -0
- ollang_sdk-0.1.0/src/ollang_sdk.egg-info/top_level.txt +1 -0
- ollang_sdk-0.1.0/tests/test_client.py +142 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ollang-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Ollang API - Translation, Transcription, Dubbing, Closed Captioning, and I18n Management
|
|
5
|
+
Author: Ollang
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ollang/sdk
|
|
8
|
+
Project-URL: Documentation, https://api-docs.ollang.com
|
|
9
|
+
Project-URL: Repository, https://github.com/ollang/sdk
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/ollang/sdk/issues
|
|
11
|
+
Keywords: ollang,translation,transcription,dubbing,closed-captioning,subtitles,localization,sdk,api
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: requests>=2.25.0
|
|
24
|
+
|
|
25
|
+
# Ollang Python SDK
|
|
26
|
+
|
|
27
|
+
Official Python SDK for the [Ollang API](https://api-docs.ollang.com) — translation, transcription, dubbing, closed captioning, and i18n management.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install ollang-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.8+.
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from ollang import Ollang
|
|
41
|
+
|
|
42
|
+
client = Ollang(api_key="your-api-key")
|
|
43
|
+
|
|
44
|
+
# Upload a file (creates a project)
|
|
45
|
+
upload = client.uploads.direct(
|
|
46
|
+
"./video.mp4",
|
|
47
|
+
name="My Video",
|
|
48
|
+
source_language="en",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Create an order
|
|
52
|
+
orders = client.orders.create(
|
|
53
|
+
order_type="cc",
|
|
54
|
+
level=1,
|
|
55
|
+
project_id=upload["projectId"],
|
|
56
|
+
target_language_configs=[
|
|
57
|
+
{"language": "fr", "isRush": False},
|
|
58
|
+
{"language": "de", "isRush": False},
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Check order status
|
|
63
|
+
status = client.orders.get(orders[0]["orderId"])
|
|
64
|
+
print(status)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Get your API key from your project settings at [Olabs](https://lab.ollang.com).
|
|
68
|
+
|
|
69
|
+
## Resources
|
|
70
|
+
|
|
71
|
+
| Resource | Description |
|
|
72
|
+
| ---------------------------- | -------------------------------------- |
|
|
73
|
+
| `client.projects` | Read and list projects |
|
|
74
|
+
| `client.uploads` | Upload files (video, audio, documents) |
|
|
75
|
+
| `client.orders` | Create and track translation orders |
|
|
76
|
+
| `client.revisions` | Request revisions on completed orders |
|
|
77
|
+
| `client.custom_instructions` | Set custom translation instructions |
|
|
78
|
+
|
|
79
|
+
All methods return the parsed JSON response as plain dicts/lists. Non-2xx
|
|
80
|
+
responses raise `ollang.OllangAPIError`, which carries `status_code` and the
|
|
81
|
+
parsed error `body`.
|
|
82
|
+
|
|
83
|
+
## Examples
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# List orders with pagination and filters
|
|
87
|
+
page = client.orders.list(page=1, take=20, status="completed")
|
|
88
|
+
for order in page["data"]:
|
|
89
|
+
print(order["id"], order.get("status"))
|
|
90
|
+
|
|
91
|
+
# Request a revision on an order
|
|
92
|
+
client.revisions.create(
|
|
93
|
+
order_id="ORDER_ID",
|
|
94
|
+
type="wrongSubtitle",
|
|
95
|
+
time="00:01:23",
|
|
96
|
+
description="Fix the terminology in this segment",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Custom instructions
|
|
100
|
+
client.custom_instructions.create(
|
|
101
|
+
key="tone",
|
|
102
|
+
value="Formal, brand-safe tone for all marketing content",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Endpoints not wrapped yet? Use the underlying HTTP client:
|
|
106
|
+
languages = client.client.get("/integration/supported-languages")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
client = Ollang(
|
|
113
|
+
api_key="your-api-key",
|
|
114
|
+
base_url="https://api-integration.ollang.com", # optional override
|
|
115
|
+
timeout=60.0, # seconds
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cd python
|
|
123
|
+
pip install -e . requests
|
|
124
|
+
python -m unittest discover tests -v
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
Full API documentation: **[https://api-docs.ollang.com](https://api-docs.ollang.com)**
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Ollang Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Ollang API](https://api-docs.ollang.com) — translation, transcription, dubbing, closed captioning, and i18n management.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install ollang-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.8+.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from ollang import Ollang
|
|
17
|
+
|
|
18
|
+
client = Ollang(api_key="your-api-key")
|
|
19
|
+
|
|
20
|
+
# Upload a file (creates a project)
|
|
21
|
+
upload = client.uploads.direct(
|
|
22
|
+
"./video.mp4",
|
|
23
|
+
name="My Video",
|
|
24
|
+
source_language="en",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Create an order
|
|
28
|
+
orders = client.orders.create(
|
|
29
|
+
order_type="cc",
|
|
30
|
+
level=1,
|
|
31
|
+
project_id=upload["projectId"],
|
|
32
|
+
target_language_configs=[
|
|
33
|
+
{"language": "fr", "isRush": False},
|
|
34
|
+
{"language": "de", "isRush": False},
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Check order status
|
|
39
|
+
status = client.orders.get(orders[0]["orderId"])
|
|
40
|
+
print(status)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Get your API key from your project settings at [Olabs](https://lab.ollang.com).
|
|
44
|
+
|
|
45
|
+
## Resources
|
|
46
|
+
|
|
47
|
+
| Resource | Description |
|
|
48
|
+
| ---------------------------- | -------------------------------------- |
|
|
49
|
+
| `client.projects` | Read and list projects |
|
|
50
|
+
| `client.uploads` | Upload files (video, audio, documents) |
|
|
51
|
+
| `client.orders` | Create and track translation orders |
|
|
52
|
+
| `client.revisions` | Request revisions on completed orders |
|
|
53
|
+
| `client.custom_instructions` | Set custom translation instructions |
|
|
54
|
+
|
|
55
|
+
All methods return the parsed JSON response as plain dicts/lists. Non-2xx
|
|
56
|
+
responses raise `ollang.OllangAPIError`, which carries `status_code` and the
|
|
57
|
+
parsed error `body`.
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# List orders with pagination and filters
|
|
63
|
+
page = client.orders.list(page=1, take=20, status="completed")
|
|
64
|
+
for order in page["data"]:
|
|
65
|
+
print(order["id"], order.get("status"))
|
|
66
|
+
|
|
67
|
+
# Request a revision on an order
|
|
68
|
+
client.revisions.create(
|
|
69
|
+
order_id="ORDER_ID",
|
|
70
|
+
type="wrongSubtitle",
|
|
71
|
+
time="00:01:23",
|
|
72
|
+
description="Fix the terminology in this segment",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Custom instructions
|
|
76
|
+
client.custom_instructions.create(
|
|
77
|
+
key="tone",
|
|
78
|
+
value="Formal, brand-safe tone for all marketing content",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Endpoints not wrapped yet? Use the underlying HTTP client:
|
|
82
|
+
languages = client.client.get("/integration/supported-languages")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
client = Ollang(
|
|
89
|
+
api_key="your-api-key",
|
|
90
|
+
base_url="https://api-integration.ollang.com", # optional override
|
|
91
|
+
timeout=60.0, # seconds
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd python
|
|
99
|
+
pip install -e . requests
|
|
100
|
+
python -m unittest discover tests -v
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Documentation
|
|
104
|
+
|
|
105
|
+
Full API documentation: **[https://api-docs.ollang.com](https://api-docs.ollang.com)**
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ollang-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Ollang API - Translation, Transcription, Dubbing, Closed Captioning, and I18n Management"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Ollang" }]
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = ["requests>=2.25.0"]
|
|
14
|
+
keywords = [
|
|
15
|
+
"ollang",
|
|
16
|
+
"translation",
|
|
17
|
+
"transcription",
|
|
18
|
+
"dubbing",
|
|
19
|
+
"closed-captioning",
|
|
20
|
+
"subtitles",
|
|
21
|
+
"localization",
|
|
22
|
+
"sdk",
|
|
23
|
+
"api",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.8",
|
|
31
|
+
"Programming Language :: Python :: 3.9",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/ollang/sdk"
|
|
39
|
+
Documentation = "https://api-docs.ollang.com"
|
|
40
|
+
Repository = "https://github.com/ollang/sdk"
|
|
41
|
+
"Bug Tracker" = "https://github.com/ollang/sdk/issues"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["src"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Official Python SDK for the Ollang API.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
|
|
5
|
+
from ollang import Ollang
|
|
6
|
+
|
|
7
|
+
client = Ollang(api_key="your-api-key")
|
|
8
|
+
projects = client.projects.list()
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from ._client import DEFAULT_BASE_URL, OllangClient
|
|
14
|
+
from ._exceptions import OllangAPIError, OllangError
|
|
15
|
+
from .resources import CustomInstructions, Orders, Projects, Revisions, Uploads
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Ollang",
|
|
21
|
+
"OllangClient",
|
|
22
|
+
"OllangError",
|
|
23
|
+
"OllangAPIError",
|
|
24
|
+
"DEFAULT_BASE_URL",
|
|
25
|
+
"__version__",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Ollang:
|
|
30
|
+
"""Entry point for the Ollang API.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
api_key: Your Ollang API key (from project settings at https://lab.ollang.com).
|
|
34
|
+
base_url: Override the API base URL. Defaults to the production
|
|
35
|
+
integration API.
|
|
36
|
+
timeout: Request timeout in seconds (default 60).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
api_key: str,
|
|
42
|
+
base_url: Optional[str] = None,
|
|
43
|
+
timeout: float = 60.0,
|
|
44
|
+
):
|
|
45
|
+
self._client = OllangClient(api_key=api_key, base_url=base_url, timeout=timeout)
|
|
46
|
+
|
|
47
|
+
self.orders = Orders(self._client)
|
|
48
|
+
self.projects = Projects(self._client)
|
|
49
|
+
self.revisions = Revisions(self._client)
|
|
50
|
+
self.uploads = Uploads(self._client)
|
|
51
|
+
self.custom_instructions = CustomInstructions(self._client)
|
|
52
|
+
|
|
53
|
+
def health_check(self) -> Dict[str, Any]:
|
|
54
|
+
return self._client.get("/health")
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def client(self) -> OllangClient:
|
|
58
|
+
"""The underlying HTTP client, for calling endpoints not yet wrapped."""
|
|
59
|
+
return self._client
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from ._exceptions import OllangAPIError
|
|
6
|
+
|
|
7
|
+
DEFAULT_BASE_URL = "https://api-integration.ollang.com"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OllangClient:
|
|
11
|
+
"""Low-level HTTP client for the Ollang integration API.
|
|
12
|
+
|
|
13
|
+
Handles authentication (``X-Api-Key`` header), JSON encoding/decoding,
|
|
14
|
+
and error mapping. Resource classes build on top of this.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
api_key: str,
|
|
20
|
+
base_url: Optional[str] = None,
|
|
21
|
+
timeout: float = 60.0,
|
|
22
|
+
session: Optional[requests.Session] = None,
|
|
23
|
+
):
|
|
24
|
+
if not api_key:
|
|
25
|
+
raise ValueError("api_key is required")
|
|
26
|
+
|
|
27
|
+
self.api_key = api_key
|
|
28
|
+
self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
29
|
+
self.timeout = timeout
|
|
30
|
+
self._session = session or requests.Session()
|
|
31
|
+
self._session.headers.update({"X-Api-Key": api_key})
|
|
32
|
+
|
|
33
|
+
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
34
|
+
return self._request("GET", path, params=params)
|
|
35
|
+
|
|
36
|
+
def post(self, path: str, json: Optional[Any] = None) -> Any:
|
|
37
|
+
return self._request("POST", path, json=json)
|
|
38
|
+
|
|
39
|
+
def patch(self, path: str, json: Optional[Any] = None) -> Any:
|
|
40
|
+
return self._request("PATCH", path, json=json)
|
|
41
|
+
|
|
42
|
+
def delete(self, path: str) -> Any:
|
|
43
|
+
return self._request("DELETE", path)
|
|
44
|
+
|
|
45
|
+
def post_multipart(self, path: str, files: Dict[str, Any], data: Dict[str, Any]) -> Any:
|
|
46
|
+
return self._request("POST", path, files=files, data=data)
|
|
47
|
+
|
|
48
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
49
|
+
url = self.base_url + path
|
|
50
|
+
response = self._session.request(method, url, timeout=self.timeout, **kwargs)
|
|
51
|
+
|
|
52
|
+
if not response.ok:
|
|
53
|
+
try:
|
|
54
|
+
body = response.json()
|
|
55
|
+
except ValueError:
|
|
56
|
+
body = response.text
|
|
57
|
+
raise OllangAPIError(
|
|
58
|
+
f"Ollang API request failed: {method} {path} -> {response.status_code}",
|
|
59
|
+
status_code=response.status_code,
|
|
60
|
+
body=body,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not response.content:
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
return response.json()
|
|
67
|
+
except ValueError:
|
|
68
|
+
return response.text
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OllangError(Exception):
|
|
5
|
+
"""Base exception for all Ollang SDK errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OllangAPIError(OllangError):
|
|
9
|
+
"""Raised when the Ollang API returns a non-2xx response.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
status_code: HTTP status code of the response.
|
|
13
|
+
body: Parsed JSON body of the error response when available,
|
|
14
|
+
otherwise the raw response text.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, status_code: Optional[int] = None, body: Any = None):
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.status_code = status_code
|
|
20
|
+
self.body = body
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from .._client import OllangClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomInstructions:
|
|
7
|
+
"""Manage custom translation instructions."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client: OllangClient):
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def list(self) -> List[Dict[str, Any]]:
|
|
13
|
+
return self._client.get("/integration/custom-instructions")
|
|
14
|
+
|
|
15
|
+
def create(self, key: str, value: str, description: Optional[str] = None) -> Dict[str, Any]:
|
|
16
|
+
body: Dict[str, Any] = {"key": key, "value": value}
|
|
17
|
+
if description is not None:
|
|
18
|
+
body["description"] = description
|
|
19
|
+
return self._client.post("/integration/custom-instructions", json=body)
|
|
20
|
+
|
|
21
|
+
def update(
|
|
22
|
+
self,
|
|
23
|
+
instruction_id: str,
|
|
24
|
+
key: Optional[str] = None,
|
|
25
|
+
value: Optional[str] = None,
|
|
26
|
+
description: Optional[str] = None,
|
|
27
|
+
) -> Dict[str, Any]:
|
|
28
|
+
body: Dict[str, Any] = {}
|
|
29
|
+
if key is not None:
|
|
30
|
+
body["key"] = key
|
|
31
|
+
if value is not None:
|
|
32
|
+
body["value"] = value
|
|
33
|
+
if description is not None:
|
|
34
|
+
body["description"] = description
|
|
35
|
+
return self._client.patch(f"/integration/custom-instructions/{instruction_id}", json=body)
|
|
36
|
+
|
|
37
|
+
def delete(self, instruction_id: str) -> Any:
|
|
38
|
+
return self._client.delete(f"/integration/custom-instructions/{instruction_id}")
|
|
39
|
+
|
|
40
|
+
def suggestions(self) -> List[Dict[str, Any]]:
|
|
41
|
+
return self._client.get("/integration/custom-instructions/suggestions")
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from .._client import OllangClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Orders:
|
|
7
|
+
"""Create and track translation orders."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client: OllangClient):
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def create(
|
|
13
|
+
self,
|
|
14
|
+
order_type: str,
|
|
15
|
+
level: int,
|
|
16
|
+
target_language_configs: List[Dict[str, Any]],
|
|
17
|
+
project_id: Optional[str] = None,
|
|
18
|
+
source_language: Optional[str] = None,
|
|
19
|
+
content: Optional[str] = None,
|
|
20
|
+
order_sub_type: Optional[str] = None,
|
|
21
|
+
dubbing_style: Optional[str] = None,
|
|
22
|
+
**extra: Any,
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
"""Create one or more orders.
|
|
25
|
+
|
|
26
|
+
``order_type`` is one of ``cc``, ``subtitle``, ``document``,
|
|
27
|
+
``aiDubbing``, ``studioDubbing``, ``proofreading``, ``other`` or
|
|
28
|
+
``revision``. Each entry of ``target_language_configs`` looks like
|
|
29
|
+
``{"language": "fr", "isRush": False}``.
|
|
30
|
+
|
|
31
|
+
Returns the raw API response: a list of ``{"orderId": ..., "orderType": ...}``
|
|
32
|
+
entries (some order types create more than one order).
|
|
33
|
+
"""
|
|
34
|
+
body: Dict[str, Any] = {
|
|
35
|
+
"orderType": order_type,
|
|
36
|
+
"level": level,
|
|
37
|
+
"targetLanguageConfigs": target_language_configs,
|
|
38
|
+
}
|
|
39
|
+
if project_id is not None:
|
|
40
|
+
body["projectId"] = project_id
|
|
41
|
+
if source_language is not None:
|
|
42
|
+
body["sourceLanguage"] = source_language
|
|
43
|
+
if content is not None:
|
|
44
|
+
body["content"] = content
|
|
45
|
+
if order_sub_type is not None:
|
|
46
|
+
body["orderSubType"] = order_sub_type
|
|
47
|
+
if dubbing_style is not None:
|
|
48
|
+
body["dubbingStyle"] = dubbing_style
|
|
49
|
+
body.update(extra)
|
|
50
|
+
|
|
51
|
+
return self._client.post("/integration/orders/create", json=body)
|
|
52
|
+
|
|
53
|
+
def list(
|
|
54
|
+
self,
|
|
55
|
+
page: Optional[int] = None,
|
|
56
|
+
take: Optional[int] = None,
|
|
57
|
+
search: Optional[str] = None,
|
|
58
|
+
order_by: Optional[str] = None,
|
|
59
|
+
order_direction: Optional[str] = None,
|
|
60
|
+
status: Optional[str] = None,
|
|
61
|
+
order_type: Optional[str] = None,
|
|
62
|
+
project_id: Optional[str] = None,
|
|
63
|
+
) -> Dict[str, Any]:
|
|
64
|
+
"""List orders with optional pagination and filters."""
|
|
65
|
+
params: Dict[str, Any] = {}
|
|
66
|
+
if page is not None:
|
|
67
|
+
params["pageOptions[page]"] = page
|
|
68
|
+
if take is not None:
|
|
69
|
+
params["pageOptions[take]"] = take
|
|
70
|
+
if search is not None:
|
|
71
|
+
params["pageOptions[search]"] = search
|
|
72
|
+
if order_by is not None:
|
|
73
|
+
params["pageOptions[orderBy]"] = order_by
|
|
74
|
+
if order_direction is not None:
|
|
75
|
+
params["pageOptions[orderDirection]"] = order_direction
|
|
76
|
+
if status is not None:
|
|
77
|
+
params["filter[status]"] = status
|
|
78
|
+
if order_type is not None:
|
|
79
|
+
params["filter[type]"] = order_type
|
|
80
|
+
if project_id is not None:
|
|
81
|
+
params["filter[projectId]"] = project_id
|
|
82
|
+
|
|
83
|
+
return self._client.get("/integration/orders", params=params)
|
|
84
|
+
|
|
85
|
+
def get(self, order_id: str) -> Dict[str, Any]:
|
|
86
|
+
return self._client.get(f"/integration/orders/{order_id}")
|
|
87
|
+
|
|
88
|
+
def cancel(self, order_id: str) -> Any:
|
|
89
|
+
return self._client.post(f"/integration/orders/cancel/{order_id}")
|
|
90
|
+
|
|
91
|
+
def request_human_review(self, order_id: str) -> Any:
|
|
92
|
+
return self._client.post(f"/integration/orders/{order_id}/human-review")
|
|
93
|
+
|
|
94
|
+
def run_qc_evaluation(
|
|
95
|
+
self,
|
|
96
|
+
order_id: str,
|
|
97
|
+
custom_prompt: Optional[str] = None,
|
|
98
|
+
accuracy: Optional[bool] = None,
|
|
99
|
+
fluency: Optional[bool] = None,
|
|
100
|
+
tone: Optional[bool] = None,
|
|
101
|
+
cultural_fit: Optional[bool] = None,
|
|
102
|
+
) -> Dict[str, Any]:
|
|
103
|
+
body: Dict[str, Any] = {}
|
|
104
|
+
if custom_prompt is not None:
|
|
105
|
+
body["customPrompt"] = custom_prompt
|
|
106
|
+
if accuracy is not None:
|
|
107
|
+
body["accuracy"] = accuracy
|
|
108
|
+
if fluency is not None:
|
|
109
|
+
body["fluency"] = fluency
|
|
110
|
+
if tone is not None:
|
|
111
|
+
body["tone"] = tone
|
|
112
|
+
if cultural_fit is not None:
|
|
113
|
+
body["culturalFit"] = cultural_fit
|
|
114
|
+
|
|
115
|
+
return self._client.post(f"/integration/orders/{order_id}/qc", json=body or None)
|
|
116
|
+
|
|
117
|
+
def rerun(self, order_id: str, free_re_run: Optional[bool] = None) -> Dict[str, Any]:
|
|
118
|
+
body = {"freeReRun": free_re_run} if free_re_run is not None else None
|
|
119
|
+
return self._client.post(f"/integration/orders/{order_id}/rerun", json=body)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from .._client import OllangClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Projects:
|
|
7
|
+
"""Read and list projects."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client: OllangClient):
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def get(self, project_id: str) -> Dict[str, Any]:
|
|
13
|
+
return self._client.get(f"/integration/project/{project_id}")
|
|
14
|
+
|
|
15
|
+
def list(
|
|
16
|
+
self,
|
|
17
|
+
page: Optional[int] = None,
|
|
18
|
+
take: Optional[int] = None,
|
|
19
|
+
search: Optional[str] = None,
|
|
20
|
+
order_by: Optional[str] = None,
|
|
21
|
+
order_direction: Optional[str] = None,
|
|
22
|
+
) -> Dict[str, Any]:
|
|
23
|
+
params: Dict[str, Any] = {}
|
|
24
|
+
if page is not None:
|
|
25
|
+
params["page"] = page
|
|
26
|
+
if take is not None:
|
|
27
|
+
params["take"] = take
|
|
28
|
+
if search is not None:
|
|
29
|
+
params["search"] = search
|
|
30
|
+
if order_by is not None:
|
|
31
|
+
params["orderBy"] = order_by
|
|
32
|
+
if order_direction is not None:
|
|
33
|
+
params["orderDirection"] = order_direction
|
|
34
|
+
|
|
35
|
+
return self._client.get("/integration/project", params=params)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from .._client import OllangClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Revisions:
|
|
7
|
+
"""Request revisions on completed orders."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client: OllangClient):
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def create(
|
|
13
|
+
self,
|
|
14
|
+
order_id: str,
|
|
15
|
+
type: str,
|
|
16
|
+
time: str,
|
|
17
|
+
description: Optional[str] = None,
|
|
18
|
+
) -> Dict[str, Any]:
|
|
19
|
+
"""Create a revision request for an order.
|
|
20
|
+
|
|
21
|
+
``type`` is one of ``missingSubtitle``, ``wrongSubtitle``, ``syncError``,
|
|
22
|
+
``formatError`` or ``other``. ``time`` is the timestamp the revision
|
|
23
|
+
refers to, e.g. ``"00:01:23"``.
|
|
24
|
+
"""
|
|
25
|
+
body: Dict[str, Any] = {"type": type, "time": time}
|
|
26
|
+
if description is not None:
|
|
27
|
+
body["description"] = description
|
|
28
|
+
return self._client.post(f"/integration/revision/{order_id}", json=body)
|
|
29
|
+
|
|
30
|
+
def list(self, order_id: str) -> List[Dict[str, Any]]:
|
|
31
|
+
return self._client.get(f"/integration/revision/{order_id}")
|
|
32
|
+
|
|
33
|
+
def delete(self, order_id: str, revision_id: str) -> Any:
|
|
34
|
+
return self._client.delete(f"/integration/revision/{order_id}/{revision_id}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, BinaryIO, Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from .._client import OllangClient
|
|
6
|
+
|
|
7
|
+
FileInput = Union[str, os.PathLike, bytes, BinaryIO]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _as_file_tuple(file: FileInput, fallback_name: str):
|
|
11
|
+
"""Normalize a path / bytes / file object into a (name, fileobj-or-bytes) tuple."""
|
|
12
|
+
if isinstance(file, (str, os.PathLike)):
|
|
13
|
+
path = os.fspath(file)
|
|
14
|
+
return (os.path.basename(path), open(path, "rb"))
|
|
15
|
+
if isinstance(file, bytes):
|
|
16
|
+
return (fallback_name, file)
|
|
17
|
+
name = getattr(file, "name", None)
|
|
18
|
+
return (os.path.basename(name) if isinstance(name, str) else fallback_name, file)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Uploads:
|
|
22
|
+
"""Upload source files (video, audio, documents) and VTT subtitle files."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, client: OllangClient):
|
|
25
|
+
self._client = client
|
|
26
|
+
|
|
27
|
+
def direct(
|
|
28
|
+
self,
|
|
29
|
+
file: FileInput,
|
|
30
|
+
name: str,
|
|
31
|
+
source_language: str,
|
|
32
|
+
notes: Optional[List[Dict[str, str]]] = None,
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
"""Upload a file directly, creating a project for it.
|
|
35
|
+
|
|
36
|
+
``file`` can be a filesystem path, raw bytes, or an open binary file
|
|
37
|
+
object. ``notes`` entries look like
|
|
38
|
+
``{"details": "...", "timeStamp": "00:01:23"}``.
|
|
39
|
+
"""
|
|
40
|
+
filename, fileobj = _as_file_tuple(file, fallback_name=name)
|
|
41
|
+
data: Dict[str, Any] = {"name": name, "sourceLanguage": source_language}
|
|
42
|
+
if notes is not None:
|
|
43
|
+
data["notes"] = json.dumps(notes)
|
|
44
|
+
try:
|
|
45
|
+
return self._client.post_multipart(
|
|
46
|
+
"/integration/upload/direct",
|
|
47
|
+
files={"file": (filename, fileobj)},
|
|
48
|
+
data=data,
|
|
49
|
+
)
|
|
50
|
+
finally:
|
|
51
|
+
if isinstance(file, (str, os.PathLike)):
|
|
52
|
+
fileobj.close()
|
|
53
|
+
|
|
54
|
+
def vtt(self, file: FileInput, order_id: str) -> Dict[str, Any]:
|
|
55
|
+
"""Upload a VTT subtitle file for an existing order."""
|
|
56
|
+
filename, fileobj = _as_file_tuple(file, fallback_name="subtitles.vtt")
|
|
57
|
+
try:
|
|
58
|
+
return self._client.post_multipart(
|
|
59
|
+
"/integration/upload/vtt",
|
|
60
|
+
files={"file": (filename, fileobj)},
|
|
61
|
+
data={"orderId": order_id},
|
|
62
|
+
)
|
|
63
|
+
finally:
|
|
64
|
+
if isinstance(file, (str, os.PathLike)):
|
|
65
|
+
fileobj.close()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ollang-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Ollang API - Translation, Transcription, Dubbing, Closed Captioning, and I18n Management
|
|
5
|
+
Author: Ollang
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ollang/sdk
|
|
8
|
+
Project-URL: Documentation, https://api-docs.ollang.com
|
|
9
|
+
Project-URL: Repository, https://github.com/ollang/sdk
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/ollang/sdk/issues
|
|
11
|
+
Keywords: ollang,translation,transcription,dubbing,closed-captioning,subtitles,localization,sdk,api
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: requests>=2.25.0
|
|
24
|
+
|
|
25
|
+
# Ollang Python SDK
|
|
26
|
+
|
|
27
|
+
Official Python SDK for the [Ollang API](https://api-docs.ollang.com) — translation, transcription, dubbing, closed captioning, and i18n management.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install ollang-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.8+.
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from ollang import Ollang
|
|
41
|
+
|
|
42
|
+
client = Ollang(api_key="your-api-key")
|
|
43
|
+
|
|
44
|
+
# Upload a file (creates a project)
|
|
45
|
+
upload = client.uploads.direct(
|
|
46
|
+
"./video.mp4",
|
|
47
|
+
name="My Video",
|
|
48
|
+
source_language="en",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Create an order
|
|
52
|
+
orders = client.orders.create(
|
|
53
|
+
order_type="cc",
|
|
54
|
+
level=1,
|
|
55
|
+
project_id=upload["projectId"],
|
|
56
|
+
target_language_configs=[
|
|
57
|
+
{"language": "fr", "isRush": False},
|
|
58
|
+
{"language": "de", "isRush": False},
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Check order status
|
|
63
|
+
status = client.orders.get(orders[0]["orderId"])
|
|
64
|
+
print(status)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Get your API key from your project settings at [Olabs](https://lab.ollang.com).
|
|
68
|
+
|
|
69
|
+
## Resources
|
|
70
|
+
|
|
71
|
+
| Resource | Description |
|
|
72
|
+
| ---------------------------- | -------------------------------------- |
|
|
73
|
+
| `client.projects` | Read and list projects |
|
|
74
|
+
| `client.uploads` | Upload files (video, audio, documents) |
|
|
75
|
+
| `client.orders` | Create and track translation orders |
|
|
76
|
+
| `client.revisions` | Request revisions on completed orders |
|
|
77
|
+
| `client.custom_instructions` | Set custom translation instructions |
|
|
78
|
+
|
|
79
|
+
All methods return the parsed JSON response as plain dicts/lists. Non-2xx
|
|
80
|
+
responses raise `ollang.OllangAPIError`, which carries `status_code` and the
|
|
81
|
+
parsed error `body`.
|
|
82
|
+
|
|
83
|
+
## Examples
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# List orders with pagination and filters
|
|
87
|
+
page = client.orders.list(page=1, take=20, status="completed")
|
|
88
|
+
for order in page["data"]:
|
|
89
|
+
print(order["id"], order.get("status"))
|
|
90
|
+
|
|
91
|
+
# Request a revision on an order
|
|
92
|
+
client.revisions.create(
|
|
93
|
+
order_id="ORDER_ID",
|
|
94
|
+
type="wrongSubtitle",
|
|
95
|
+
time="00:01:23",
|
|
96
|
+
description="Fix the terminology in this segment",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Custom instructions
|
|
100
|
+
client.custom_instructions.create(
|
|
101
|
+
key="tone",
|
|
102
|
+
value="Formal, brand-safe tone for all marketing content",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Endpoints not wrapped yet? Use the underlying HTTP client:
|
|
106
|
+
languages = client.client.get("/integration/supported-languages")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
client = Ollang(
|
|
113
|
+
api_key="your-api-key",
|
|
114
|
+
base_url="https://api-integration.ollang.com", # optional override
|
|
115
|
+
timeout=60.0, # seconds
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cd python
|
|
123
|
+
pip install -e . requests
|
|
124
|
+
python -m unittest discover tests -v
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
Full API documentation: **[https://api-docs.ollang.com](https://api-docs.ollang.com)**
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ollang/__init__.py
|
|
4
|
+
src/ollang/_client.py
|
|
5
|
+
src/ollang/_exceptions.py
|
|
6
|
+
src/ollang/resources/__init__.py
|
|
7
|
+
src/ollang/resources/custom_instructions.py
|
|
8
|
+
src/ollang/resources/orders.py
|
|
9
|
+
src/ollang/resources/projects.py
|
|
10
|
+
src/ollang/resources/revisions.py
|
|
11
|
+
src/ollang/resources/uploads.py
|
|
12
|
+
src/ollang_sdk.egg-info/PKG-INFO
|
|
13
|
+
src/ollang_sdk.egg-info/SOURCES.txt
|
|
14
|
+
src/ollang_sdk.egg-info/dependency_links.txt
|
|
15
|
+
src/ollang_sdk.egg-info/requires.txt
|
|
16
|
+
src/ollang_sdk.egg-info/top_level.txt
|
|
17
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ollang
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from ollang import Ollang, OllangAPIError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FakeResponse:
|
|
8
|
+
def __init__(self, status_code=200, body=None):
|
|
9
|
+
self.status_code = status_code
|
|
10
|
+
self._body = body
|
|
11
|
+
self.content = b"" if body is None else json.dumps(body).encode()
|
|
12
|
+
self.text = "" if body is None else json.dumps(body)
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def ok(self):
|
|
16
|
+
return self.status_code < 400
|
|
17
|
+
|
|
18
|
+
def json(self):
|
|
19
|
+
if self._body is None:
|
|
20
|
+
raise ValueError("no body")
|
|
21
|
+
return self._body
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FakeSession:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.headers = {}
|
|
27
|
+
self.calls = []
|
|
28
|
+
self.next_response = FakeResponse(200, {"ok": True})
|
|
29
|
+
|
|
30
|
+
def request(self, method, url, **kwargs):
|
|
31
|
+
self.calls.append({"method": method, "url": url, **kwargs})
|
|
32
|
+
return self.next_response
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def make_client(session):
|
|
36
|
+
client = Ollang(api_key="test-key")
|
|
37
|
+
# Swap in the fake transport so no network is used.
|
|
38
|
+
client.client._session = session
|
|
39
|
+
session.headers.update({"X-Api-Key": "test-key"})
|
|
40
|
+
return client
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ClientTests(unittest.TestCase):
|
|
44
|
+
def setUp(self):
|
|
45
|
+
self.session = FakeSession()
|
|
46
|
+
self.ollang = make_client(self.session)
|
|
47
|
+
|
|
48
|
+
def test_requires_api_key(self):
|
|
49
|
+
with self.assertRaises(ValueError):
|
|
50
|
+
Ollang(api_key="")
|
|
51
|
+
|
|
52
|
+
def test_health_check_url_and_auth(self):
|
|
53
|
+
self.ollang.health_check()
|
|
54
|
+
call = self.session.calls[0]
|
|
55
|
+
self.assertEqual(call["method"], "GET")
|
|
56
|
+
self.assertEqual(call["url"], "https://api-integration.ollang.com/health")
|
|
57
|
+
self.assertEqual(self.session.headers["X-Api-Key"], "test-key")
|
|
58
|
+
|
|
59
|
+
def test_custom_base_url_trailing_slash(self):
|
|
60
|
+
client = Ollang(api_key="k", base_url="https://example.com/")
|
|
61
|
+
client.client._session = self.session
|
|
62
|
+
client.health_check()
|
|
63
|
+
self.assertEqual(self.session.calls[0]["url"], "https://example.com/health")
|
|
64
|
+
|
|
65
|
+
def test_orders_create_body(self):
|
|
66
|
+
self.session.next_response = FakeResponse(200, [{"orderId": "o1"}])
|
|
67
|
+
result = self.ollang.orders.create(
|
|
68
|
+
order_type="cc",
|
|
69
|
+
level=1,
|
|
70
|
+
project_id="p1",
|
|
71
|
+
target_language_configs=[{"language": "fr", "isRush": False}],
|
|
72
|
+
)
|
|
73
|
+
call = self.session.calls[0]
|
|
74
|
+
self.assertEqual(call["method"], "POST")
|
|
75
|
+
self.assertTrue(call["url"].endswith("/integration/orders/create"))
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
call["json"],
|
|
78
|
+
{
|
|
79
|
+
"orderType": "cc",
|
|
80
|
+
"level": 1,
|
|
81
|
+
"targetLanguageConfigs": [{"language": "fr", "isRush": False}],
|
|
82
|
+
"projectId": "p1",
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
self.assertEqual(result, [{"orderId": "o1"}])
|
|
86
|
+
|
|
87
|
+
def test_orders_list_params(self):
|
|
88
|
+
self.ollang.orders.list(page=2, take=10, status="completed", project_id="p1")
|
|
89
|
+
call = self.session.calls[0]
|
|
90
|
+
self.assertEqual(
|
|
91
|
+
call["params"],
|
|
92
|
+
{
|
|
93
|
+
"pageOptions[page]": 2,
|
|
94
|
+
"pageOptions[take]": 10,
|
|
95
|
+
"filter[status]": "completed",
|
|
96
|
+
"filter[projectId]": "p1",
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def test_revisions_paths(self):
|
|
101
|
+
self.ollang.revisions.create("o1", type="wrongSubtitle", time="00:01:23", description="typo")
|
|
102
|
+
self.ollang.revisions.list("o1")
|
|
103
|
+
self.ollang.revisions.delete("o1", "r1")
|
|
104
|
+
self.assertTrue(self.session.calls[0]["url"].endswith("/integration/revision/o1"))
|
|
105
|
+
self.assertEqual(self.session.calls[1]["method"], "GET")
|
|
106
|
+
self.assertEqual(self.session.calls[2]["method"], "DELETE")
|
|
107
|
+
self.assertTrue(self.session.calls[2]["url"].endswith("/integration/revision/o1/r1"))
|
|
108
|
+
|
|
109
|
+
def test_upload_direct_multipart(self):
|
|
110
|
+
self.ollang.uploads.direct(
|
|
111
|
+
b"fake-bytes",
|
|
112
|
+
name="clip.mp4",
|
|
113
|
+
source_language="en",
|
|
114
|
+
notes=[{"details": "intro", "timeStamp": "00:00:01"}],
|
|
115
|
+
)
|
|
116
|
+
call = self.session.calls[0]
|
|
117
|
+
self.assertTrue(call["url"].endswith("/integration/upload/direct"))
|
|
118
|
+
self.assertIn("file", call["files"])
|
|
119
|
+
self.assertEqual(call["data"]["name"], "clip.mp4")
|
|
120
|
+
self.assertEqual(call["data"]["sourceLanguage"], "en")
|
|
121
|
+
self.assertEqual(
|
|
122
|
+
json.loads(call["data"]["notes"]),
|
|
123
|
+
[{"details": "intro", "timeStamp": "00:00:01"}],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def test_custom_instructions_update(self):
|
|
127
|
+
self.ollang.custom_instructions.update("ci1", value="new value")
|
|
128
|
+
call = self.session.calls[0]
|
|
129
|
+
self.assertEqual(call["method"], "PATCH")
|
|
130
|
+
self.assertTrue(call["url"].endswith("/integration/custom-instructions/ci1"))
|
|
131
|
+
self.assertEqual(call["json"], {"value": "new value"})
|
|
132
|
+
|
|
133
|
+
def test_api_error_raised(self):
|
|
134
|
+
self.session.next_response = FakeResponse(401, {"message": "invalid key"})
|
|
135
|
+
with self.assertRaises(OllangAPIError) as ctx:
|
|
136
|
+
self.ollang.projects.list()
|
|
137
|
+
self.assertEqual(ctx.exception.status_code, 401)
|
|
138
|
+
self.assertEqual(ctx.exception.body, {"message": "invalid key"})
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
unittest.main()
|