lavendly 0.0.1__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.
- lavendly-0.0.1/PKG-INFO +82 -0
- lavendly-0.0.1/README.md +61 -0
- lavendly-0.0.1/lavendly.egg-info/PKG-INFO +82 -0
- lavendly-0.0.1/lavendly.egg-info/SOURCES.txt +8 -0
- lavendly-0.0.1/lavendly.egg-info/dependency_links.txt +1 -0
- lavendly-0.0.1/lavendly.egg-info/requires.txt +1 -0
- lavendly-0.0.1/lavendly.egg-info/top_level.txt +1 -0
- lavendly-0.0.1/pyproject.toml +33 -0
- lavendly-0.0.1/setup.cfg +4 -0
- lavendly-0.0.1/tests/test_sdk.py +106 -0
lavendly-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lavendly
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Official Python SDK for Lavendly - generate AI video with one API call.
|
|
5
|
+
Author: Lavendly
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://docs.lavendly.ai/sdks/python
|
|
8
|
+
Project-URL: Repository, https://github.com/barlou83/lavendly
|
|
9
|
+
Project-URL: Documentation, https://docs.lavendly.ai
|
|
10
|
+
Keywords: lavendly,ai-video,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: httpx>=0.24
|
|
21
|
+
|
|
22
|
+
# lavendly
|
|
23
|
+
|
|
24
|
+
Official Python SDK for Lavendly.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install lavendly
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from lavendly import Lavendly
|
|
32
|
+
|
|
33
|
+
vs = Lavendly(api_key="lv_...")
|
|
34
|
+
|
|
35
|
+
wf = vs.workflows.create(
|
|
36
|
+
name="Fox in a bookshop",
|
|
37
|
+
shots=[{"prompt": "a sleepy fox in a bookshop", "duration": 5}],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
video = vs.renders.run(wf["id"])
|
|
41
|
+
print(video.url)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Highlights
|
|
45
|
+
|
|
46
|
+
- **Sync + async.** Same surface; `Lavendly` and `AsyncLavendly`.
|
|
47
|
+
- **Idempotent by default.** Every mutating method accepts `idempotency_key`.
|
|
48
|
+
- **One-call renders.** `vs.renders.run()` creates + polls to completion.
|
|
49
|
+
- **Tool-use dispatcher.** `vs.call(operation, args)` for the Anthropic SDK tool_use pattern.
|
|
50
|
+
- **Bundled skills.** `from lavendly.skills import load_skill` returns the markdown body of `storyteller`, `multi-clip`, `cost-aware`.
|
|
51
|
+
|
|
52
|
+
## Async
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import asyncio
|
|
56
|
+
from lavendly import AsyncLavendly
|
|
57
|
+
|
|
58
|
+
async def main():
|
|
59
|
+
async with AsyncLavendly(api_key="...") as lavendly:
|
|
60
|
+
wf = await vs.workflows.create(name="x", shots=[...])
|
|
61
|
+
return await vs.renders.run(wf["id"])
|
|
62
|
+
|
|
63
|
+
result = asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Errors
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from lavendly import LavendlyError
|
|
70
|
+
try:
|
|
71
|
+
vs.workflows.create(name="...", shots=[...])
|
|
72
|
+
except LavendlyError as e:
|
|
73
|
+
print(e.code, e.status, e.details)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Full docs
|
|
77
|
+
|
|
78
|
+
<https://docs.lavendly.ai/sdks/python>
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
lavendly-0.0.1/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# lavendly
|
|
2
|
+
|
|
3
|
+
Official Python SDK for Lavendly.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install lavendly
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from lavendly import Lavendly
|
|
11
|
+
|
|
12
|
+
vs = Lavendly(api_key="lv_...")
|
|
13
|
+
|
|
14
|
+
wf = vs.workflows.create(
|
|
15
|
+
name="Fox in a bookshop",
|
|
16
|
+
shots=[{"prompt": "a sleepy fox in a bookshop", "duration": 5}],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
video = vs.renders.run(wf["id"])
|
|
20
|
+
print(video.url)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Highlights
|
|
24
|
+
|
|
25
|
+
- **Sync + async.** Same surface; `Lavendly` and `AsyncLavendly`.
|
|
26
|
+
- **Idempotent by default.** Every mutating method accepts `idempotency_key`.
|
|
27
|
+
- **One-call renders.** `vs.renders.run()` creates + polls to completion.
|
|
28
|
+
- **Tool-use dispatcher.** `vs.call(operation, args)` for the Anthropic SDK tool_use pattern.
|
|
29
|
+
- **Bundled skills.** `from lavendly.skills import load_skill` returns the markdown body of `storyteller`, `multi-clip`, `cost-aware`.
|
|
30
|
+
|
|
31
|
+
## Async
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
from lavendly import AsyncLavendly
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
async with AsyncLavendly(api_key="...") as lavendly:
|
|
39
|
+
wf = await vs.workflows.create(name="x", shots=[...])
|
|
40
|
+
return await vs.renders.run(wf["id"])
|
|
41
|
+
|
|
42
|
+
result = asyncio.run(main())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Errors
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from lavendly import LavendlyError
|
|
49
|
+
try:
|
|
50
|
+
vs.workflows.create(name="...", shots=[...])
|
|
51
|
+
except LavendlyError as e:
|
|
52
|
+
print(e.code, e.status, e.details)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Full docs
|
|
56
|
+
|
|
57
|
+
<https://docs.lavendly.ai/sdks/python>
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lavendly
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Official Python SDK for Lavendly - generate AI video with one API call.
|
|
5
|
+
Author: Lavendly
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://docs.lavendly.ai/sdks/python
|
|
8
|
+
Project-URL: Repository, https://github.com/barlou83/lavendly
|
|
9
|
+
Project-URL: Documentation, https://docs.lavendly.ai
|
|
10
|
+
Keywords: lavendly,ai-video,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: httpx>=0.24
|
|
21
|
+
|
|
22
|
+
# lavendly
|
|
23
|
+
|
|
24
|
+
Official Python SDK for Lavendly.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install lavendly
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from lavendly import Lavendly
|
|
32
|
+
|
|
33
|
+
vs = Lavendly(api_key="lv_...")
|
|
34
|
+
|
|
35
|
+
wf = vs.workflows.create(
|
|
36
|
+
name="Fox in a bookshop",
|
|
37
|
+
shots=[{"prompt": "a sleepy fox in a bookshop", "duration": 5}],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
video = vs.renders.run(wf["id"])
|
|
41
|
+
print(video.url)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Highlights
|
|
45
|
+
|
|
46
|
+
- **Sync + async.** Same surface; `Lavendly` and `AsyncLavendly`.
|
|
47
|
+
- **Idempotent by default.** Every mutating method accepts `idempotency_key`.
|
|
48
|
+
- **One-call renders.** `vs.renders.run()` creates + polls to completion.
|
|
49
|
+
- **Tool-use dispatcher.** `vs.call(operation, args)` for the Anthropic SDK tool_use pattern.
|
|
50
|
+
- **Bundled skills.** `from lavendly.skills import load_skill` returns the markdown body of `storyteller`, `multi-clip`, `cost-aware`.
|
|
51
|
+
|
|
52
|
+
## Async
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import asyncio
|
|
56
|
+
from lavendly import AsyncLavendly
|
|
57
|
+
|
|
58
|
+
async def main():
|
|
59
|
+
async with AsyncLavendly(api_key="...") as lavendly:
|
|
60
|
+
wf = await vs.workflows.create(name="x", shots=[...])
|
|
61
|
+
return await vs.renders.run(wf["id"])
|
|
62
|
+
|
|
63
|
+
result = asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Errors
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from lavendly import LavendlyError
|
|
70
|
+
try:
|
|
71
|
+
vs.workflows.create(name="...", shots=[...])
|
|
72
|
+
except LavendlyError as e:
|
|
73
|
+
print(e.code, e.status, e.details)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Full docs
|
|
77
|
+
|
|
78
|
+
<https://docs.lavendly.ai/sdks/python>
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
httpx>=0.24
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lavendly"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Official Python SDK for Lavendly - generate AI video with one API call."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Lavendly" }]
|
|
13
|
+
keywords = ["lavendly", "ai-video", "sdk"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.24",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://docs.lavendly.ai/sdks/python"
|
|
29
|
+
Repository = "https://github.com/barlou83/lavendly"
|
|
30
|
+
Documentation = "https://docs.lavendly.ai"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
include = ["lavendly*"]
|
lavendly-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Offline tests for the Python SDK using httpx MockTransport.
|
|
2
|
+
|
|
3
|
+
Run: python -m unittest tests/test_sdk.py
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import unittest
|
|
10
|
+
import pathlib
|
|
11
|
+
|
|
12
|
+
# Allow running directly: tests/ is sibling of lavendly/
|
|
13
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
from lavendly import Lavendly, LavendlyError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_transport(handler):
|
|
20
|
+
calls = []
|
|
21
|
+
def _handle(request):
|
|
22
|
+
body = json.loads(request.content) if request.content else None
|
|
23
|
+
calls.append({
|
|
24
|
+
"method": request.method,
|
|
25
|
+
"url": str(request.url),
|
|
26
|
+
"headers": dict(request.headers),
|
|
27
|
+
"body": body,
|
|
28
|
+
})
|
|
29
|
+
out = handler(request, calls) or {}
|
|
30
|
+
return httpx.Response(
|
|
31
|
+
status_code=out.get("status", 200),
|
|
32
|
+
json=out.get("json", {}),
|
|
33
|
+
)
|
|
34
|
+
return httpx.MockTransport(_handle), calls
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TestSDK(unittest.TestCase):
|
|
38
|
+
|
|
39
|
+
def test_missing_api_key(self):
|
|
40
|
+
with self.assertRaises(LavendlyError) as ctx:
|
|
41
|
+
Lavendly(api_key="")
|
|
42
|
+
self.assertEqual(ctx.exception.code, "missing_api_key")
|
|
43
|
+
|
|
44
|
+
def test_workflow_create_idempotency(self):
|
|
45
|
+
transport, calls = make_transport(
|
|
46
|
+
lambda req, calls: {"json": {"id": "wf_xyz", "name": "fox"}}
|
|
47
|
+
)
|
|
48
|
+
vs = Lavendly(api_key="lv_test_123", transport=transport)
|
|
49
|
+
wf = vs.workflows.create(name="fox", shots=[{"prompt": "x", "duration": 5}],
|
|
50
|
+
idempotency_key="create-fox")
|
|
51
|
+
self.assertEqual(wf["id"], "wf_xyz")
|
|
52
|
+
self.assertEqual(calls[0]["method"], "POST")
|
|
53
|
+
self.assertTrue(calls[0]["url"].endswith("/v1/workflows"))
|
|
54
|
+
self.assertEqual(calls[0]["headers"]["authorization"], "Bearer lv_test_123")
|
|
55
|
+
self.assertEqual(calls[0]["headers"]["idempotency-key"], "create-fox")
|
|
56
|
+
self.assertNotIn("idempotency_key", calls[0]["body"])
|
|
57
|
+
|
|
58
|
+
def test_render_run_polls_to_done(self):
|
|
59
|
+
states = [
|
|
60
|
+
{"id": "j", "status": "queued"},
|
|
61
|
+
{"id": "j", "status": "running", "progress": 0.5},
|
|
62
|
+
{"id": "j", "status": "done", "result": {"video_url": "https://x.mp4", "duration": 5}},
|
|
63
|
+
]
|
|
64
|
+
idx = {"n": -1}
|
|
65
|
+
|
|
66
|
+
def h(req, calls):
|
|
67
|
+
if req.method == "POST":
|
|
68
|
+
return {"json": {"job_id": "j", "status": "queued"}}
|
|
69
|
+
idx["n"] += 1
|
|
70
|
+
return {"json": states[min(idx["n"], len(states) - 1)]}
|
|
71
|
+
|
|
72
|
+
transport, calls = make_transport(h)
|
|
73
|
+
vs = Lavendly(api_key="k", transport=transport)
|
|
74
|
+
ticks = []
|
|
75
|
+
result = vs.renders.run("wf", poll_interval=0.001, max_wait=5,
|
|
76
|
+
on_progress=lambda j: ticks.append(j["status"]))
|
|
77
|
+
self.assertEqual(result.url, "https://x.mp4")
|
|
78
|
+
self.assertEqual(result.duration, 5)
|
|
79
|
+
self.assertEqual(ticks[-1], "done")
|
|
80
|
+
|
|
81
|
+
def test_error_envelope(self):
|
|
82
|
+
transport, _ = make_transport(
|
|
83
|
+
lambda req, calls: {
|
|
84
|
+
"status": 402,
|
|
85
|
+
"json": {"error": {"code": "insufficient_credits",
|
|
86
|
+
"message": "too poor", "needed": 10, "have": 4}},
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
vs = Lavendly(api_key="k", transport=transport)
|
|
90
|
+
with self.assertRaises(LavendlyError) as ctx:
|
|
91
|
+
vs.ledger.reserve(10)
|
|
92
|
+
self.assertEqual(ctx.exception.code, "insufficient_credits")
|
|
93
|
+
self.assertEqual(ctx.exception.status, 402)
|
|
94
|
+
self.assertEqual(ctx.exception.details["needed"], 10)
|
|
95
|
+
|
|
96
|
+
def test_call_dispatcher(self):
|
|
97
|
+
transport, calls = make_transport(
|
|
98
|
+
lambda req, c: {"json": {"id": "wf_x"}}
|
|
99
|
+
)
|
|
100
|
+
vs = Lavendly(api_key="k", transport=transport)
|
|
101
|
+
out = vs.call("create_workflow", {"name": "n", "shots": []})
|
|
102
|
+
self.assertEqual(out["id"], "wf_x")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
unittest.main()
|