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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,7 @@
1
+ from .custom_instructions import CustomInstructions
2
+ from .orders import Orders
3
+ from .projects import Projects
4
+ from .revisions import Revisions
5
+ from .uploads import Uploads
6
+
7
+ __all__ = ["CustomInstructions", "Orders", "Projects", "Revisions", "Uploads"]
@@ -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
+ requests>=2.25.0
@@ -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()