llamagen-python 0.1.3__tar.gz → 0.1.5__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.
Files changed (20) hide show
  1. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/.gitignore +1 -0
  2. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/PKG-INFO +1 -1
  3. llamagen_python-0.1.5/examples/quickstart.py +40 -0
  4. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/pyproject.toml +1 -1
  5. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/__init__.py +1 -1
  6. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/_http.py +6 -2
  7. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/resources/animations.py +9 -2
  8. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/resources/comics.py +9 -2
  9. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/tests/test_client.py +36 -1
  10. llamagen_python-0.1.3/examples/quickstart.py +0 -23
  11. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/LICENSE +0 -0
  12. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/README.md +0 -0
  13. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/docs/RELEASE.md +0 -0
  14. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/_client.py +0 -0
  15. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/_errors.py +0 -0
  16. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/_types.py +0 -0
  17. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/_webhooks.py +0 -0
  18. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/py.typed +0 -0
  19. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/src/llamagen/resources/__init__.py +0 -0
  20. {llamagen_python-0.1.3 → llamagen_python-0.1.5}/tests/test_webhooks.py +0 -0
@@ -6,6 +6,7 @@
6
6
  /.ruff_cache/
7
7
  /.mypy_cache/
8
8
  /.venv/
9
+ /.env
9
10
  __pycache__/
10
11
  .ipynb_checkpoints/
11
12
  *.py[cod]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llamagen-python
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Official Python SDK for LlamaGen Comic API and Animation API
5
5
  Project-URL: Homepage, https://llamagen.ai/comic-api
6
6
  Project-URL: Documentation, https://llamagen.ai/comic-api/docs
@@ -0,0 +1,40 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from llamagen import LlamaGenAPIError, LlamaGenConnectionError, LlamaGenClient, LlamaGenTimeoutError
5
+
6
+
7
+ def load_dotenv(path: Path) -> None:
8
+ if not path.exists():
9
+ return
10
+
11
+ for raw_line in path.read_text(encoding="utf-8").splitlines():
12
+ line = raw_line.strip()
13
+ if not line or line.startswith("#") or "=" not in line:
14
+ continue
15
+ key, value = line.split("=", 1)
16
+ key = key.strip()
17
+ value = value.strip().strip('"').strip("'")
18
+ os.environ.setdefault(key, value)
19
+
20
+
21
+ load_dotenv(Path(__file__).resolve().parents[1] / ".env")
22
+ client = LlamaGenClient(api_key=os.environ["LLAMAGEN_API_KEY"], max_retries=5)
23
+
24
+ try:
25
+ created = client.comic.create(
26
+ {
27
+ "prompt": "A 4-panel comic about Leo finding a glowing key in a quiet library.",
28
+ "style": "manga",
29
+ "fixPanelNum": 4,
30
+ }
31
+ )
32
+ print("created:", created["id"], created.get("status"))
33
+ done = client.comic.wait_for_completion(created["id"])
34
+ print("done:", done["status"])
35
+ except LlamaGenAPIError as error:
36
+ print("LlamaGen API error:", error.status, error.data)
37
+ except LlamaGenConnectionError as error:
38
+ print("LlamaGen connection error:", error)
39
+ except LlamaGenTimeoutError as error:
40
+ print("LlamaGen timeout:", error)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "llamagen-python"
7
- version = "0.1.3"
7
+ version = "0.1.5"
8
8
  description = "Official Python SDK for LlamaGen Comic API and Animation API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -19,4 +19,4 @@ __all__ = [
19
19
  "verify_webhook_signature",
20
20
  ]
21
21
 
22
- __version__ = "0.1.3"
22
+ __version__ = "0.1.5"
@@ -10,7 +10,7 @@ from typing import Any, Dict, Mapping, Optional, Protocol, Tuple
10
10
 
11
11
  from ._errors import LlamaGenAPIError, LlamaGenConnectionError, LlamaGenTimeoutError
12
12
 
13
- DEFAULT_USER_AGENT = "llamagen-python/0.1.3"
13
+ DEFAULT_USER_AGENT = "llamagen-python/0.1.5"
14
14
 
15
15
 
16
16
  class Transport(Protocol):
@@ -26,6 +26,10 @@ class Transport(Protocol):
26
26
 
27
27
 
28
28
  class UrllibTransport:
29
+ def __init__(self) -> None:
30
+ self.context = ssl.create_default_context()
31
+ self.context.minimum_version = ssl.TLSVersion.TLSv1_2
32
+
29
33
  def request(
30
34
  self,
31
35
  method: str,
@@ -36,7 +40,7 @@ class UrllibTransport:
36
40
  ) -> Tuple[int, Mapping[str, str], bytes]:
37
41
  request = urllib.request.Request(url, data=body, method=method, headers=dict(headers))
38
42
  try:
39
- with urllib.request.urlopen(request, timeout=timeout) as response:
43
+ with urllib.request.urlopen(request, timeout=timeout, context=self.context) as response:
40
44
  return response.status, dict(response.headers.items()), response.read()
41
45
  except urllib.error.HTTPError as error:
42
46
  return error.code, dict(error.headers.items()), error.read()
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import time
4
4
  from typing import Mapping, Optional, Sequence
5
5
 
6
- from .._errors import LlamaGenTimeoutError
6
+ from .._errors import LlamaGenConnectionError, LlamaGenTimeoutError
7
7
  from .._http import HTTPClient
8
8
  from .._types import AnimationArtworkResponse
9
9
 
@@ -34,7 +34,14 @@ class AnimationsResource:
34
34
  start = time.monotonic()
35
35
 
36
36
  while True:
37
- result = self.get(artwork_id)
37
+ try:
38
+ result = self.get(artwork_id)
39
+ except (LlamaGenConnectionError, LlamaGenTimeoutError):
40
+ if (time.monotonic() - start) * 1000 >= timeout_ms:
41
+ raise
42
+ time.sleep(interval_ms / 1000)
43
+ continue
44
+
38
45
  if str(result.get("status", "")).upper() in done:
39
46
  return result
40
47
 
@@ -9,7 +9,7 @@ from pathlib import Path
9
9
  from typing import Any, BinaryIO, Dict, Iterable, List, Mapping, Optional, Sequence, Union
10
10
  from urllib.parse import urlencode
11
11
 
12
- from .._errors import LlamaGenTimeoutError
12
+ from .._errors import LlamaGenConnectionError, LlamaGenTimeoutError
13
13
  from .._http import HTTPClient
14
14
  from .._types import ComicArtworkResponse, ComicUploadResponse, ComicUsage, SUPPORTED_COMIC_SIZES
15
15
 
@@ -99,7 +99,14 @@ class ComicsResource:
99
99
  start = time.monotonic()
100
100
 
101
101
  while True:
102
- result = self.get(artwork_id)
102
+ try:
103
+ result = self.get(artwork_id)
104
+ except (LlamaGenConnectionError, LlamaGenTimeoutError):
105
+ if (time.monotonic() - start) * 1000 >= timeout_ms:
106
+ raise
107
+ time.sleep(interval_ms / 1000)
108
+ continue
109
+
103
110
  if str(result.get("status", "")).upper() in done:
104
111
  return result
105
112
 
@@ -44,6 +44,27 @@ class FailingTransport:
44
44
  raise self.errors.pop(0)
45
45
 
46
46
 
47
+ class MixedTransport:
48
+ def __init__(self, events):
49
+ self.events = list(events)
50
+ self.calls = []
51
+
52
+ def request(
53
+ self,
54
+ method: str,
55
+ url: str,
56
+ headers: Mapping[str, str],
57
+ body: Optional[bytes],
58
+ timeout: float,
59
+ ) -> Tuple[int, Mapping[str, str], bytes]:
60
+ self.calls.append({"method": method, "url": url, "headers": dict(headers), "body": body})
61
+ event = self.events.pop(0)
62
+ if isinstance(event, BaseException):
63
+ raise event
64
+ status, payload = event
65
+ return status, {"content-type": "application/json"}, json.dumps(payload).encode("utf-8")
66
+
67
+
47
68
  class ClientTests(unittest.TestCase):
48
69
  def test_create_comic_applies_defaults_and_auth(self):
49
70
  transport = FakeTransport([(200, {"id": "gen_1", "status": "PENDING"})])
@@ -57,7 +78,7 @@ class ClientTests(unittest.TestCase):
57
78
  self.assertEqual(call["url"], "https://api.llamagen.ai/v1/comics/generations")
58
79
  self.assertEqual(call["headers"]["Authorization"], "Bearer test-key")
59
80
  self.assertEqual(call["headers"]["Accept"], "application/json")
60
- self.assertEqual(call["headers"]["User-Agent"], "llamagen-python/0.1.3")
81
+ self.assertEqual(call["headers"]["User-Agent"], "llamagen-python/0.1.5")
61
82
  body = json.loads(call["body"].decode("utf-8"))
62
83
  self.assertEqual(body["preset"], "neutral")
63
84
  self.assertEqual(body["size"], "1024x1024")
@@ -122,6 +143,20 @@ class ClientTests(unittest.TestCase):
122
143
 
123
144
  self.assertEqual(len(transport.calls), 3)
124
145
 
146
+ def test_wait_for_completion_continues_after_transient_connection_error(self):
147
+ transport = MixedTransport(
148
+ [
149
+ urllib.error.URLError(ssl.SSLEOFError("unexpected eof")),
150
+ (200, {"id": "gen_1", "status": "SUCCEEDED"}),
151
+ ]
152
+ )
153
+ client = LlamaGenClient(api_key="test-key", max_retries=0, retry_delay_ms=1, transport=transport)
154
+
155
+ result = client.comic.wait_for_completion("gen_1", interval_ms=1, timeout_ms=1000)
156
+
157
+ self.assertEqual(result["status"], "SUCCEEDED")
158
+ self.assertEqual(len(transport.calls), 2)
159
+
125
160
 
126
161
  if __name__ == "__main__":
127
162
  unittest.main()
@@ -1,23 +0,0 @@
1
- import os
2
-
3
- from llamagen import LlamaGenAPIError, LlamaGenConnectionError, LlamaGenClient, LlamaGenTimeoutError
4
-
5
-
6
- client = LlamaGenClient(api_key=os.environ["LLAMAGEN_API_KEY"])
7
-
8
- try:
9
- created = client.comic.create(
10
- {
11
- "prompt": "A 4-panel comic about Leo finding a glowing key in a quiet library.",
12
- "style": "manga",
13
- "fixPanelNum": 4,
14
- }
15
- )
16
- done = client.comic.wait_for_completion(created["id"])
17
- print(done["status"])
18
- except LlamaGenAPIError as error:
19
- print("LlamaGen API error:", error.status, error.data)
20
- except LlamaGenConnectionError as error:
21
- print("LlamaGen connection error:", error)
22
- except LlamaGenTimeoutError as error:
23
- print("LlamaGen timeout:", error)
File without changes