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.
@@ -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,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,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ lavendly.egg-info/PKG-INFO
4
+ lavendly.egg-info/SOURCES.txt
5
+ lavendly.egg-info/dependency_links.txt
6
+ lavendly.egg-info/requires.txt
7
+ lavendly.egg-info/top_level.txt
8
+ tests/test_sdk.py
@@ -0,0 +1 @@
1
+ httpx>=0.24
@@ -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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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()