runapi-runway-aleph 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,29 @@
1
+ # Build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ *.egg
6
+
7
+ # Bytecode
8
+ __pycache__/
9
+ *.py[cod]
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+
15
+ # uv
16
+ uv.lock
17
+
18
+ # Test / type caches
19
+ .pytest_cache/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .coverage
23
+ htmlcov/
24
+
25
+ # IDE / OS
26
+ .idea/
27
+ .vscode/
28
+ *.swp
29
+ .DS_Store
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: runapi-runway-aleph
3
+ Version: 0.1.0
4
+ Summary: Runway Aleph prompt-guided video editing client for RunAPI
5
+ Project-URL: Homepage, https://runapi.ai/models/runway-aleph
6
+ Project-URL: Documentation, https://runapi.ai/docs#sdk-runway-aleph
7
+ Author-email: RunAPI <contact@runapi.ai>
8
+ License-Expression: Apache-2.0
9
+ Keywords: ai,runapi,runway,runway-aleph,sdk,video
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: runapi-core
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Runway Aleph Python SDK for RunAPI
15
+
16
+ The Runway Aleph Python SDK is the language-specific package for Runway Aleph on
17
+ RunAPI. Use it for prompt-guided video editing when your application needs JSON
18
+ request bodies, task status lookup, and consistent RunAPI errors in Python.
19
+
20
+ For model details, use https://runapi.ai/models/runway-aleph; for API reference,
21
+ use https://runapi.ai/docs#runway-aleph; for SDK docs, use
22
+ https://runapi.ai/docs#sdk-runway-aleph.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install runapi-runway-aleph
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```python
33
+ from runapi.runway_aleph import RunwayAlephClient
34
+
35
+ client = RunwayAlephClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
36
+
37
+ task = client.edit_video.create(
38
+ model="runway-aleph",
39
+ prompt="Transform the scene into a watercolor painting style",
40
+ source_video_url="https://cdn.runapi.ai/public/samples/video.mp4",
41
+ )
42
+ status = client.edit_video.get(task.id)
43
+ ```
44
+
45
+ Use `create` to submit a task and return quickly, `get` to fetch the latest task
46
+ state, and `run` to create and poll until completion:
47
+
48
+ ```python
49
+ result = client.edit_video.run(
50
+ model="runway-aleph",
51
+ prompt="Transform the scene into a watercolor painting style",
52
+ source_video_url="https://cdn.runapi.ai/public/samples/video.mp4",
53
+ )
54
+ print(result.videos[0].url)
55
+ ```
56
+
57
+ In web request handlers, prefer `create` plus webhook or later `get` polling so a
58
+ worker is not held open.
59
+
60
+ RunAPI-generated file URLs are temporary. Download and store generated videos in
61
+ your own durable storage within 7 days; do not treat returned URLs as long-term
62
+ assets.
63
+
64
+ ## Language notes
65
+
66
+ Pass parameters as keyword arguments and catch the `runapi.runway_aleph` error
67
+ classes when building video jobs, workers, or scripts. The available resource is
68
+ `edit_video`. Keep `RUNAPI_API_KEY` in the environment or your secret manager;
69
+ never commit API keys or callback secrets.
70
+
71
+ ## Links
72
+
73
+ - Model page: https://runapi.ai/models/runway-aleph
74
+ - SDK docs: https://runapi.ai/docs#sdk-runway-aleph
75
+ - Product docs: https://runapi.ai/docs#runway-aleph
76
+ - Pricing and rate limits: https://runapi.ai/models/runway-aleph
77
+ - Full catalog: https://runapi.ai/models
78
+
79
+ ## License
80
+
81
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,68 @@
1
+ # Runway Aleph Python SDK for RunAPI
2
+
3
+ The Runway Aleph Python SDK is the language-specific package for Runway Aleph on
4
+ RunAPI. Use it for prompt-guided video editing when your application needs JSON
5
+ request bodies, task status lookup, and consistent RunAPI errors in Python.
6
+
7
+ For model details, use https://runapi.ai/models/runway-aleph; for API reference,
8
+ use https://runapi.ai/docs#runway-aleph; for SDK docs, use
9
+ https://runapi.ai/docs#sdk-runway-aleph.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install runapi-runway-aleph
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```python
20
+ from runapi.runway_aleph import RunwayAlephClient
21
+
22
+ client = RunwayAlephClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
23
+
24
+ task = client.edit_video.create(
25
+ model="runway-aleph",
26
+ prompt="Transform the scene into a watercolor painting style",
27
+ source_video_url="https://cdn.runapi.ai/public/samples/video.mp4",
28
+ )
29
+ status = client.edit_video.get(task.id)
30
+ ```
31
+
32
+ Use `create` to submit a task and return quickly, `get` to fetch the latest task
33
+ state, and `run` to create and poll until completion:
34
+
35
+ ```python
36
+ result = client.edit_video.run(
37
+ model="runway-aleph",
38
+ prompt="Transform the scene into a watercolor painting style",
39
+ source_video_url="https://cdn.runapi.ai/public/samples/video.mp4",
40
+ )
41
+ print(result.videos[0].url)
42
+ ```
43
+
44
+ In web request handlers, prefer `create` plus webhook or later `get` polling so a
45
+ worker is not held open.
46
+
47
+ RunAPI-generated file URLs are temporary. Download and store generated videos in
48
+ your own durable storage within 7 days; do not treat returned URLs as long-term
49
+ assets.
50
+
51
+ ## Language notes
52
+
53
+ Pass parameters as keyword arguments and catch the `runapi.runway_aleph` error
54
+ classes when building video jobs, workers, or scripts. The available resource is
55
+ `edit_video`. Keep `RUNAPI_API_KEY` in the environment or your secret manager;
56
+ never commit API keys or callback secrets.
57
+
58
+ ## Links
59
+
60
+ - Model page: https://runapi.ai/models/runway-aleph
61
+ - SDK docs: https://runapi.ai/docs#sdk-runway-aleph
62
+ - Product docs: https://runapi.ai/docs#runway-aleph
63
+ - Pricing and rate limits: https://runapi.ai/models/runway-aleph
64
+ - Full catalog: https://runapi.ai/models
65
+
66
+ ## License
67
+
68
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "runapi-runway-aleph"
7
+ version = "0.1.0"
8
+ description = "Runway Aleph prompt-guided video editing client for RunAPI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
13
+ keywords = ["runapi", "runway", "runway-aleph", "video", "ai", "sdk"]
14
+ dependencies = ["runapi-core"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://runapi.ai/models/runway-aleph"
18
+ Documentation = "https://runapi.ai/docs#sdk-runway-aleph"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/runapi"]
22
+
23
+ [tool.uv]
24
+ package = true
25
+
26
+ [tool.uv.sources]
27
+ runapi-core = { workspace = true }
28
+
29
+ [dependency-groups]
30
+ dev = ["pytest>=8"]
31
+
32
+ [tool.runapi]
33
+ slug = "runway-aleph"
@@ -0,0 +1,24 @@
1
+ """Runway Aleph client for RunAPI."""
2
+
3
+ from runapi.core import (
4
+ AuthenticationError,
5
+ InsufficientCreditsError,
6
+ NotFoundError,
7
+ RateLimitError,
8
+ TaskFailedError,
9
+ TaskTimeoutError,
10
+ ValidationError,
11
+ )
12
+
13
+ from .client import RunwayAlephClient
14
+
15
+ __all__ = [
16
+ "RunwayAlephClient",
17
+ "AuthenticationError",
18
+ "RateLimitError",
19
+ "InsufficientCreditsError",
20
+ "NotFoundError",
21
+ "ValidationError",
22
+ "TaskFailedError",
23
+ "TaskTimeoutError",
24
+ ]
@@ -0,0 +1,29 @@
1
+ """Runway Aleph client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from runapi.core import ClientOptions, HttpClient, resolve_api_key
8
+
9
+ from .resources.edit_video import EditVideo
10
+
11
+
12
+ class RunwayAlephClient:
13
+ """Runway Aleph prompt-guided video editing client.
14
+
15
+ Example::
16
+
17
+ client = RunwayAlephClient(api_key="sk-...")
18
+ result = client.edit_video.run(
19
+ model="runway-aleph",
20
+ prompt="Transform the scene into a watercolor painting style",
21
+ source_video_url="https://cdn.runapi.ai/public/samples/video.mp4",
22
+ )
23
+ """
24
+
25
+ def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
26
+ resolved_api_key = resolve_api_key(api_key)
27
+ client_options = ClientOptions(api_key=resolved_api_key, **options)
28
+ http = client_options.http_client or HttpClient(client_options)
29
+ self.edit_video = EditVideo(http)
@@ -0,0 +1,21 @@
1
+ CONTRACT = {
2
+ "edit-video": {
3
+ "models": ["runway-aleph"],
4
+ "fields_by_model": {
5
+ "runway-aleph": {
6
+ "aspect_ratio": {
7
+ "enum": ["16:9", "9:16", "4:3", "3:4", "1:1", "21:9"]
8
+ },
9
+ "prompt": {
10
+ "required": True
11
+ },
12
+ "seed": {
13
+ "type": "integer"
14
+ },
15
+ "source_video_url": {
16
+ "required": True
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ from .edit_video import EditVideo
2
+
3
+ __all__ = ["EditVideo"]
@@ -0,0 +1,39 @@
1
+ """Runway Aleph edit-video resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from runapi.core import Resource
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import (
11
+ CompletedEditVideoResponse,
12
+ TaskCreateResponse,
13
+ )
14
+
15
+
16
+ class EditVideo(Resource):
17
+ """Prompt-guided video editing with Runway Aleph."""
18
+
19
+ ENDPOINT = "/api/v1/runway_aleph/edit_video"
20
+
21
+ RESPONSE_CLASS = TaskCreateResponse
22
+ COMPLETED_RESPONSE_CLASS = CompletedEditVideoResponse
23
+
24
+ MODEL = "runway-aleph"
25
+
26
+ def run(self, **params: Any) -> Any:
27
+ """Create a task and poll until it completes."""
28
+ task = self.create(**params)
29
+ return self._poll_until_complete(lambda: self.get(task.id))
30
+
31
+ def create(self, **params: Any) -> Any:
32
+ """Create an edit-video task and return immediately with an ``id``."""
33
+ compacted = self._compact_params(params)
34
+ self._validate_contract(CONTRACT["edit-video"], {**compacted, "model": self.MODEL})
35
+ return self._request("post", self.ENDPOINT, body=compacted)
36
+
37
+ def get(self, id: str) -> Any:
38
+ """Fetch the current status of an edit-video task."""
39
+ return self._request("get", f"{self.ENDPOINT}/{id}")
@@ -0,0 +1,37 @@
1
+ """Runway Aleph enums and response models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from runapi.core import BaseModel, TaskResponse, optional, required
6
+
7
+
8
+ class Video(BaseModel):
9
+ id = optional(str)
10
+ url = required(str)
11
+
12
+
13
+ class Image(BaseModel):
14
+ url = required(str)
15
+
16
+
17
+ class EditVideoResponse(TaskResponse):
18
+ """Runway Aleph video editing task status response."""
19
+
20
+ id = required(str)
21
+ status = optional(str, enum=lambda: TaskResponse.Status.ALL)
22
+ videos = optional([lambda: Video])
23
+ images = optional([lambda: Image])
24
+ error = optional(str)
25
+
26
+
27
+ class TaskCreateResponse(EditVideoResponse):
28
+ """Runway Aleph task creation response with an id."""
29
+
30
+
31
+ class CompletedEditVideoResponse(EditVideoResponse):
32
+ """Returned by ``edit_video.run()`` once polling observes completion.
33
+
34
+ ``videos`` is required so callers never have to null-check it on success.
35
+ """
36
+
37
+ videos = required([lambda: Video])
@@ -0,0 +1,161 @@
1
+ import pytest
2
+
3
+ from runapi.core import config
4
+ from runapi.core.errors import AuthenticationError, ValidationError
5
+ from runapi.runway_aleph import RunwayAlephClient
6
+ from runapi.runway_aleph.resources.edit_video import EditVideo
7
+ from runapi.runway_aleph.types import CompletedEditVideoResponse, EditVideoResponse
8
+
9
+
10
+ class FakeHttp:
11
+ """Records (method, path, body) and replays preset responses by call order."""
12
+
13
+ def __init__(self, *responses):
14
+ self._responses = list(responses)
15
+ self.calls = []
16
+
17
+ def request(self, method, path, body=None, options=None):
18
+ self.calls.append((method, path, body))
19
+ if self._responses:
20
+ return self._responses.pop(0)
21
+ return {"id": "task_1", "status": "pending"}
22
+
23
+
24
+ @pytest.fixture(autouse=True)
25
+ def reset_config(monkeypatch):
26
+ monkeypatch.delenv("RUNAPI_API_KEY", raising=False)
27
+ monkeypatch.setattr(config, "api_key", None)
28
+ yield
29
+
30
+
31
+ # --- authentication -------------------------------------------------------
32
+
33
+
34
+ def test_accepts_api_key_parameter():
35
+ assert isinstance(
36
+ RunwayAlephClient(api_key="param-key", http_client=FakeHttp()), RunwayAlephClient
37
+ )
38
+
39
+
40
+ def test_falls_back_to_global(monkeypatch):
41
+ monkeypatch.setattr(config, "api_key", "global-key")
42
+ assert isinstance(RunwayAlephClient(http_client=FakeHttp()), RunwayAlephClient)
43
+
44
+
45
+ def test_falls_back_to_env(monkeypatch):
46
+ monkeypatch.setenv("RUNAPI_API_KEY", "env-key")
47
+ assert isinstance(RunwayAlephClient(http_client=FakeHttp()), RunwayAlephClient)
48
+
49
+
50
+ def test_raises_without_api_key():
51
+ with pytest.raises(AuthenticationError, match="API key is required"):
52
+ RunwayAlephClient()
53
+
54
+
55
+ # --- transport injection / accessors --------------------------------------
56
+
57
+
58
+ def test_uses_injected_http_client():
59
+ fake = FakeHttp()
60
+ client = RunwayAlephClient(api_key="k", http_client=fake)
61
+ assert client.edit_video._http is fake
62
+
63
+
64
+ def test_exposes_resource_accessors():
65
+ client = RunwayAlephClient(api_key="k", http_client=FakeHttp())
66
+ assert isinstance(client.edit_video, EditVideo)
67
+
68
+
69
+ # --- request shapes -------------------------------------------------------
70
+
71
+
72
+ def test_create_posts_compacted_body():
73
+ fake = FakeHttp({"id": "t1", "status": "pending"})
74
+ client = RunwayAlephClient(api_key="k", http_client=fake)
75
+ result = client.edit_video.create(
76
+ prompt="hello",
77
+ source_video_url="https://example.com/v.mp4",
78
+ aspect_ratio="16:9",
79
+ seed=None,
80
+ )
81
+ assert fake.calls == [
82
+ (
83
+ "post",
84
+ "/api/v1/runway_aleph/edit_video",
85
+ {
86
+ "prompt": "hello",
87
+ "source_video_url": "https://example.com/v.mp4",
88
+ "aspect_ratio": "16:9",
89
+ },
90
+ ),
91
+ ]
92
+ _, _, body = fake.calls[0]
93
+ assert "model" not in body
94
+ assert isinstance(result, EditVideoResponse)
95
+ assert result.id == "t1"
96
+
97
+
98
+ def test_get_fetches_by_id():
99
+ fake = FakeHttp({"id": "t1", "status": "processing"})
100
+ client = RunwayAlephClient(api_key="k", http_client=fake)
101
+ client.edit_video.get("t1")
102
+ assert fake.calls == [("get", "/api/v1/runway_aleph/edit_video/t1", None)]
103
+
104
+
105
+ def test_run_polls_and_narrows_completed_type():
106
+ fake = FakeHttp(
107
+ {"id": "t1", "status": "pending"},
108
+ {"id": "t1", "status": "completed", "videos": [{"url": "https://x/y.mp4"}]},
109
+ )
110
+ client = RunwayAlephClient(api_key="k", http_client=fake)
111
+ result = client.edit_video.run(
112
+ model="runway-aleph",
113
+ prompt="hi",
114
+ source_video_url="https://example.com/v.mp4",
115
+ )
116
+
117
+ assert isinstance(result, CompletedEditVideoResponse)
118
+ assert result.videos[0].url == "https://x/y.mp4"
119
+ assert [call[0] for call in fake.calls] == ["post", "get"]
120
+
121
+
122
+ # --- validation -----------------------------------------------------------
123
+
124
+
125
+ def test_create_requires_prompt():
126
+ client = RunwayAlephClient(api_key="k", http_client=FakeHttp())
127
+ with pytest.raises(ValidationError, match="prompt is required"):
128
+ client.edit_video.create(
129
+ model="runway-aleph", source_video_url="https://example.com/v.mp4"
130
+ )
131
+
132
+
133
+ def test_create_requires_source_video_url():
134
+ client = RunwayAlephClient(api_key="k", http_client=FakeHttp())
135
+ with pytest.raises(ValidationError, match="source_video_url is required"):
136
+ client.edit_video.create(model="runway-aleph", prompt="hi")
137
+
138
+
139
+ def test_create_rejects_invalid_aspect_ratio():
140
+ client = RunwayAlephClient(api_key="k", http_client=FakeHttp())
141
+ with pytest.raises(ValidationError, match="aspect_ratio must be one of"):
142
+ client.edit_video.create(
143
+ model="runway-aleph",
144
+ prompt="hi",
145
+ source_video_url="https://example.com/v.mp4",
146
+ aspect_ratio="99:1",
147
+ )
148
+
149
+
150
+ def test_create_accepts_valid_aspect_ratio():
151
+ fake = FakeHttp({"id": "t1", "status": "pending"})
152
+ client = RunwayAlephClient(api_key="k", http_client=fake)
153
+ client.edit_video.create(
154
+ model="runway-aleph",
155
+ prompt="hi",
156
+ source_video_url="https://example.com/v.mp4",
157
+ aspect_ratio="21:9",
158
+ )
159
+ _, path, body = fake.calls[0]
160
+ assert path == "/api/v1/runway_aleph/edit_video"
161
+ assert body["aspect_ratio"] == "21:9"