reve 0.1.0__py3-none-any.whl
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.
- reve/__init__.py +4 -0
- reve/_client.py +180 -0
- reve/_response.py +81 -0
- reve/exceptions.py +115 -0
- reve/v1/__init__.py +2 -0
- reve/v1/image.py +321 -0
- reve/v1/postprocessing.py +87 -0
- reve-0.1.0.dist-info/METADATA +276 -0
- reve-0.1.0.dist-info/RECORD +11 -0
- reve-0.1.0.dist-info/WHEEL +5 -0
- reve-0.1.0.dist-info/top_level.txt +1 -0
reve/__init__.py
ADDED
reve/_client.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""HTTP client for the Reve API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
import requests as _requests
|
|
7
|
+
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
ReveAPIError,
|
|
10
|
+
ReveAuthenticationError,
|
|
11
|
+
ReveBudgetExhaustedError,
|
|
12
|
+
ReveContentViolationError,
|
|
13
|
+
ReveRateLimitError,
|
|
14
|
+
ReveValidationError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_DEFAULT_API_URL = "https://api.reve.com"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ReveClient:
|
|
22
|
+
"""Low-level HTTP client for the Reve API.
|
|
23
|
+
|
|
24
|
+
Handles authentication, base URL resolution, and error mapping.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
api_token: Bearer token. Falls back to the ``REVE_API_TOKEN``
|
|
28
|
+
environment variable if not provided.
|
|
29
|
+
api_url: Base API URL. Falls back to ``REVE_MONOLITH_LOCATION``
|
|
30
|
+
env var, then defaults to ``https://api.reve.com``.
|
|
31
|
+
proxy_authorization: Optional proxy-authorization header value
|
|
32
|
+
(e.g. for Google IAP). Falls back to
|
|
33
|
+
``REVE_PROXY_AUTHORIZATION`` env var.
|
|
34
|
+
verify: SSL certificate verification. Pass ``False`` to disable
|
|
35
|
+
SSL verification (e.g. for local development). Defaults to
|
|
36
|
+
``True``.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
api_token: Optional[str] = None,
|
|
42
|
+
api_url: Optional[str] = None,
|
|
43
|
+
proxy_authorization: Optional[str] = None,
|
|
44
|
+
verify: bool = True,
|
|
45
|
+
) -> None:
|
|
46
|
+
self.api_token: Optional[str] = api_token or os.environ.get("REVE_API_TOKEN")
|
|
47
|
+
self.api_url: str = (
|
|
48
|
+
api_url
|
|
49
|
+
or os.environ.get("REVE_MONOLITH_LOCATION")
|
|
50
|
+
or _DEFAULT_API_URL
|
|
51
|
+
)
|
|
52
|
+
# Strip trailing slash from base URL to avoid double slashes
|
|
53
|
+
self.api_url = self.api_url.rstrip("/")
|
|
54
|
+
self.proxy_authorization: Optional[str] = (
|
|
55
|
+
proxy_authorization
|
|
56
|
+
or os.environ.get("REVE_PROXY_AUTHORIZATION")
|
|
57
|
+
)
|
|
58
|
+
self.verify: bool = verify
|
|
59
|
+
|
|
60
|
+
def _headers(self, accept: str = "application/json") -> Dict[str, str]:
|
|
61
|
+
"""Build request headers including auth and accept type.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
accept: Value for the Accept header.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dict of HTTP headers.
|
|
68
|
+
"""
|
|
69
|
+
headers: Dict[str, str] = {"Accept": accept}
|
|
70
|
+
if self.api_token:
|
|
71
|
+
headers["Authorization"] = "Bearer {}".format(self.api_token)
|
|
72
|
+
if self.proxy_authorization:
|
|
73
|
+
headers["proxy-authorization"] = self.proxy_authorization
|
|
74
|
+
return headers
|
|
75
|
+
|
|
76
|
+
def _handle_error(self, response: _requests.Response) -> None:
|
|
77
|
+
"""Raise an appropriate exception for error responses.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
response: The HTTP response with a 4xx/5xx status code.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ReveValidationError: For HTTP 400.
|
|
84
|
+
ReveAuthenticationError: For HTTP 401.
|
|
85
|
+
ReveBudgetExhaustedError: For HTTP 402.
|
|
86
|
+
ReveRateLimitError: For HTTP 429.
|
|
87
|
+
ReveAPIError: For all other error status codes.
|
|
88
|
+
"""
|
|
89
|
+
status = response.status_code
|
|
90
|
+
try:
|
|
91
|
+
body = response.json()
|
|
92
|
+
except ValueError:
|
|
93
|
+
body = {}
|
|
94
|
+
|
|
95
|
+
message = body.get("message") or body.get("error") or response.text
|
|
96
|
+
error_code = body.get("error_code")
|
|
97
|
+
|
|
98
|
+
if status == 400:
|
|
99
|
+
raise ReveValidationError(message=message, error_code=error_code)
|
|
100
|
+
if status == 401:
|
|
101
|
+
raise ReveAuthenticationError(message=message, error_code=error_code)
|
|
102
|
+
if status == 402:
|
|
103
|
+
raise ReveBudgetExhaustedError(message=message, error_code=error_code)
|
|
104
|
+
if status == 429:
|
|
105
|
+
retry_after: Optional[Union[float, str]] = response.headers.get("Retry-After")
|
|
106
|
+
if retry_after is not None:
|
|
107
|
+
try:
|
|
108
|
+
retry_after = float(retry_after)
|
|
109
|
+
except (ValueError, TypeError):
|
|
110
|
+
pass
|
|
111
|
+
raise ReveRateLimitError(
|
|
112
|
+
message=message, retry_after=retry_after, error_code=error_code
|
|
113
|
+
)
|
|
114
|
+
raise ReveAPIError(
|
|
115
|
+
message=message, status_code=status, error_code=error_code
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def post(
|
|
119
|
+
self,
|
|
120
|
+
path: str,
|
|
121
|
+
data: Dict[str, Any],
|
|
122
|
+
accept: str = "application/json",
|
|
123
|
+
) -> Union[Dict[str, Any], Tuple[bytes, Any]]:
|
|
124
|
+
"""Send a POST request to the Reve API.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
path: API path (e.g. ``"/v1/image/create/"``).
|
|
128
|
+
data: JSON-serializable request body.
|
|
129
|
+
accept: Accept header value. Use ``"image/jpeg"`` for image
|
|
130
|
+
endpoints.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Parsed JSON dict if *accept* is ``"application/json"``, otherwise
|
|
134
|
+
a tuple of ``(raw_bytes, response_headers)``.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
ReveAPIError: If the server returns an error status code.
|
|
138
|
+
"""
|
|
139
|
+
url = self.api_url + path
|
|
140
|
+
headers = self._headers(accept=accept)
|
|
141
|
+
headers["Content-Type"] = "application/json"
|
|
142
|
+
|
|
143
|
+
resp = _requests.post(url, json=data, headers=headers, verify=self.verify)
|
|
144
|
+
|
|
145
|
+
if resp.status_code >= 400:
|
|
146
|
+
self._handle_error(resp)
|
|
147
|
+
|
|
148
|
+
if accept == "application/json":
|
|
149
|
+
return resp.json()
|
|
150
|
+
|
|
151
|
+
# For image responses, return bytes + headers for metadata extraction
|
|
152
|
+
return resp.content, resp.headers
|
|
153
|
+
|
|
154
|
+
def get(
|
|
155
|
+
self,
|
|
156
|
+
path: str,
|
|
157
|
+
params: Optional[Dict[str, str]] = None,
|
|
158
|
+
) -> Any:
|
|
159
|
+
"""Send a GET request to the Reve API.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
path: API path (e.g. ``"/v1/image/balance/"``).
|
|
163
|
+
params: Optional query parameters dict.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Parsed JSON response.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ReveAPIError: If the server returns an error status code.
|
|
170
|
+
"""
|
|
171
|
+
url = self.api_url + path
|
|
172
|
+
headers = self._headers(accept="application/json")
|
|
173
|
+
|
|
174
|
+
resp = _requests.get(url, params=params, headers=headers, verify=self.verify)
|
|
175
|
+
|
|
176
|
+
if resp.status_code >= 400:
|
|
177
|
+
self._handle_error(resp)
|
|
178
|
+
|
|
179
|
+
return resp.json()
|
|
180
|
+
|
reve/_response.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Response classes for the Reve Python SDK."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from PIL import Image as PILImage
|
|
7
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ImageResponse(BaseModel):
|
|
11
|
+
"""Response from an image generation API call.
|
|
12
|
+
|
|
13
|
+
Contains the generated image as a PIL Image object along with
|
|
14
|
+
metadata about the request.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
image: PIL.Image.Image object containing the generated image.
|
|
18
|
+
request_id: Unique identifier for the API request.
|
|
19
|
+
credits_used: Number of API credits consumed by this request.
|
|
20
|
+
credits_remaining: Number of API credits remaining in the budget.
|
|
21
|
+
version: Model version used for generation.
|
|
22
|
+
content_violation: Whether a content policy violation was detected.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
26
|
+
|
|
27
|
+
image: PILImage.Image
|
|
28
|
+
request_id: Optional[str] = None
|
|
29
|
+
credits_used: Optional[int] = None
|
|
30
|
+
credits_remaining: Optional[int] = None
|
|
31
|
+
version: Optional[str] = None
|
|
32
|
+
content_violation: bool = False
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_raw(
|
|
36
|
+
cls,
|
|
37
|
+
image_bytes: bytes,
|
|
38
|
+
request_id: Optional[str] = None,
|
|
39
|
+
credits_used: Optional[str] = None,
|
|
40
|
+
credits_remaining: Optional[str] = None,
|
|
41
|
+
version: Optional[str] = None,
|
|
42
|
+
content_violation: bool = False,
|
|
43
|
+
) -> "ImageResponse":
|
|
44
|
+
"""Create an ImageResponse from raw image bytes and header values.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
image_bytes: Raw image file bytes (JPEG, PNG, etc.).
|
|
48
|
+
request_id: Value from x-reve-request-id header.
|
|
49
|
+
credits_used: Value from x-reve-credits-used header (string).
|
|
50
|
+
credits_remaining: Value from x-reve-credits-remaining header (string).
|
|
51
|
+
version: Value from x-reve-version header.
|
|
52
|
+
content_violation: Whether content violation was flagged.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
ImageResponse with parsed image and metadata.
|
|
56
|
+
"""
|
|
57
|
+
img = PILImage.open(io.BytesIO(image_bytes))
|
|
58
|
+
return cls(
|
|
59
|
+
image=img,
|
|
60
|
+
request_id=request_id,
|
|
61
|
+
credits_used=int(credits_used) if credits_used is not None else None,
|
|
62
|
+
credits_remaining=int(credits_remaining) if credits_remaining is not None else None,
|
|
63
|
+
version=version,
|
|
64
|
+
content_violation=content_violation,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def save(self, *args, **kwargs):
|
|
68
|
+
"""Save the image to a file. Delegates to PIL.Image.save().
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
*args: Positional arguments passed to PIL.Image.save().
|
|
72
|
+
**kwargs: Keyword arguments passed to PIL.Image.save().
|
|
73
|
+
"""
|
|
74
|
+
return self.image.save(*args, **kwargs)
|
|
75
|
+
|
|
76
|
+
def __repr__(self) -> str:
|
|
77
|
+
w, h = self.image.size
|
|
78
|
+
return "<ImageResponse request_id={!r} size={}x{}>".format(
|
|
79
|
+
self.request_id, w, h
|
|
80
|
+
)
|
|
81
|
+
|
reve/exceptions.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Exception classes for the Reve Python SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ReveAPIError(Exception):
|
|
7
|
+
"""Base exception for all Reve API errors.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
message: Human-readable error description.
|
|
11
|
+
status_code: HTTP status code that triggered the error, if any.
|
|
12
|
+
error_code: Machine-readable error code from the API, if any.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
message: str,
|
|
18
|
+
status_code: Optional[int] = None,
|
|
19
|
+
error_code: Optional[str] = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
self.message = message
|
|
22
|
+
self.status_code = status_code
|
|
23
|
+
self.error_code = error_code
|
|
24
|
+
super().__init__(self.message)
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
parts = []
|
|
28
|
+
if self.status_code is not None:
|
|
29
|
+
parts.append("status={}".format(self.status_code))
|
|
30
|
+
if self.error_code is not None:
|
|
31
|
+
parts.append("error_code={}".format(self.error_code))
|
|
32
|
+
parts.append(self.message)
|
|
33
|
+
return " ".join(parts)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ReveAuthenticationError(ReveAPIError):
|
|
37
|
+
"""Raised when authentication fails (HTTP 401).
|
|
38
|
+
|
|
39
|
+
This typically means the API token is missing, expired, or invalid.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
message: str = "Authentication failed",
|
|
45
|
+
error_code: Optional[str] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
super().__init__(message, status_code=401, error_code=error_code)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReveBudgetExhaustedError(ReveAPIError):
|
|
51
|
+
"""Raised when the credit budget is exhausted (HTTP 402).
|
|
52
|
+
|
|
53
|
+
Check your balance with :func:`reve.v1.image.get_balance` and
|
|
54
|
+
top up credits to continue.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
message: str = "Budget exhausted",
|
|
60
|
+
error_code: Optional[str] = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
super().__init__(message, status_code=402, error_code=error_code)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ReveRateLimitError(ReveAPIError):
|
|
66
|
+
"""Raised when the rate limit is exceeded (HTTP 429).
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
retry_after: Seconds to wait before retrying, if provided by the API.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
message: str = "Rate limit exceeded",
|
|
75
|
+
retry_after: Optional[Union[float, str]] = None,
|
|
76
|
+
error_code: Optional[str] = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
self.retry_after = retry_after
|
|
79
|
+
super().__init__(message, status_code=429, error_code=error_code)
|
|
80
|
+
|
|
81
|
+
def __str__(self) -> str:
|
|
82
|
+
base = super().__str__()
|
|
83
|
+
if self.retry_after is not None:
|
|
84
|
+
return "{} (retry_after={})".format(base, self.retry_after)
|
|
85
|
+
return base
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ReveContentViolationError(ReveAPIError):
|
|
89
|
+
"""Raised when the content violates safety policies.
|
|
90
|
+
|
|
91
|
+
This can occur either on input (prompt/images) or on the generated output.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
message: str = "Content violation detected",
|
|
97
|
+
status_code: Optional[int] = None,
|
|
98
|
+
error_code: Optional[str] = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
super().__init__(message, status_code=status_code, error_code=error_code)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ReveValidationError(ReveAPIError):
|
|
104
|
+
"""Raised when the request fails validation (HTTP 400).
|
|
105
|
+
|
|
106
|
+
Check the ``message`` attribute for details on which parameter was invalid.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
message: str = "Validation error",
|
|
112
|
+
error_code: Optional[str] = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
super().__init__(message, status_code=400, error_code=error_code)
|
|
115
|
+
|
reve/v1/__init__.py
ADDED
reve/v1/image.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""Reve v1 image API functions.
|
|
2
|
+
|
|
3
|
+
Provides high-level functions for image generation, remixing, editing,
|
|
4
|
+
balance checking, and effect listing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
import io
|
|
9
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
10
|
+
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
from .._client import ReveClient
|
|
14
|
+
from .._response import ImageResponse
|
|
15
|
+
from ..exceptions import ReveContentViolationError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#: Types accepted as image inputs for remix/edit.
|
|
19
|
+
ImageInput = Union[str, bytes, Image.Image]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _encode_image(img: ImageInput) -> str:
|
|
23
|
+
"""Convert an image input to a base64-encoded string.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
img: An image as a file path (str), raw bytes, or PIL Image.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Base64-encoded string of the image data.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
TypeError: If ``img`` is not a supported type.
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(img, str):
|
|
35
|
+
with open(img, "rb") as f:
|
|
36
|
+
data = f.read()
|
|
37
|
+
elif isinstance(img, bytes):
|
|
38
|
+
data = img
|
|
39
|
+
elif isinstance(img, Image.Image):
|
|
40
|
+
buf = io.BytesIO()
|
|
41
|
+
img.save(buf, format="JPEG")
|
|
42
|
+
data = buf.getvalue()
|
|
43
|
+
else:
|
|
44
|
+
raise TypeError(
|
|
45
|
+
"Image must be a file path (str), bytes, or PIL.Image; got {}".format(
|
|
46
|
+
type(img).__name__
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
return base64.b64encode(data).decode("ascii")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _make_client(
|
|
53
|
+
api_token: Optional[str] = None,
|
|
54
|
+
api_url: Optional[str] = None,
|
|
55
|
+
proxy_authorization: Optional[str] = None,
|
|
56
|
+
verify: bool = True,
|
|
57
|
+
) -> ReveClient:
|
|
58
|
+
"""Create a ReveClient from explicit args or environment."""
|
|
59
|
+
return ReveClient(
|
|
60
|
+
api_token=api_token,
|
|
61
|
+
api_url=api_url,
|
|
62
|
+
proxy_authorization=proxy_authorization,
|
|
63
|
+
verify=verify,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _parse_image_response(raw_bytes: bytes, headers: Any) -> ImageResponse:
|
|
68
|
+
"""Parse raw image bytes and response headers into an ImageResponse.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
raw_bytes: Raw image bytes from the API response.
|
|
72
|
+
headers: Response headers containing metadata.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
An ImageResponse wrapping the decoded image and metadata.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ReveContentViolationError: If the response indicates a content violation.
|
|
79
|
+
"""
|
|
80
|
+
content_violation = headers.get("x-reve-content-violation", "").lower() in (
|
|
81
|
+
"true", "1", "yes",
|
|
82
|
+
)
|
|
83
|
+
if content_violation:
|
|
84
|
+
raise ReveContentViolationError(
|
|
85
|
+
message="Content violation detected in response"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return ImageResponse.from_raw(
|
|
89
|
+
image_bytes=raw_bytes,
|
|
90
|
+
request_id=headers.get("x-reve-request-id"),
|
|
91
|
+
credits_used=headers.get("x-reve-credits-used"),
|
|
92
|
+
credits_remaining=headers.get("x-reve-credits-remaining"),
|
|
93
|
+
version=headers.get("x-reve-version"),
|
|
94
|
+
content_violation=content_violation,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
_EXCLUDE_KEYS = frozenset(("api_token", "api_url", "proxy_authorization", "verify", "client"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_body(params: Dict[str, Any], exclude: frozenset = _EXCLUDE_KEYS) -> Dict[str, Any]:
|
|
102
|
+
"""Build a JSON body dict, excluding client-config keys and None values."""
|
|
103
|
+
return {k: v for k, v in params.items() if k not in exclude and v is not None}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def create(
|
|
107
|
+
prompt: str,
|
|
108
|
+
*,
|
|
109
|
+
aspect_ratio: Optional[str] = None,
|
|
110
|
+
version: Optional[str] = None,
|
|
111
|
+
test_time_scaling: Optional[int] = None,
|
|
112
|
+
postprocessing: Optional[List[Dict[str, Any]]] = None,
|
|
113
|
+
api_token: Optional[str] = None,
|
|
114
|
+
api_url: Optional[str] = None,
|
|
115
|
+
proxy_authorization: Optional[str] = None,
|
|
116
|
+
verify: bool = True,
|
|
117
|
+
) -> ImageResponse:
|
|
118
|
+
"""Generate an image from a text prompt.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
prompt: Text description of the image to generate.
|
|
122
|
+
aspect_ratio: Aspect ratio string. One of ``"16:9"``, ``"3:2"``,
|
|
123
|
+
``"4:3"``, ``"1:1"``, ``"3:4"``, ``"2:3"``, ``"9:16"``, or
|
|
124
|
+
``"auto"`` (default ``"auto"``).
|
|
125
|
+
version: Model version identifier or ``"latest"`` (default).
|
|
126
|
+
test_time_scaling: Quality scaling factor from 1 to 5. Higher values
|
|
127
|
+
produce better quality but consume more credits.
|
|
128
|
+
postprocessing: List of postprocessing operation dicts built with
|
|
129
|
+
helpers from :mod:`reve.v1.postprocessing`.
|
|
130
|
+
api_token: Override the API token for this request. Falls back to
|
|
131
|
+
the ``REVE_API_TOKEN`` environment variable.
|
|
132
|
+
api_url: Override the base API URL. Falls back to
|
|
133
|
+
``REVE_MONOLITH_LOCATION`` or ``https://api.reve.com``.
|
|
134
|
+
proxy_authorization: Override the proxy-authorization header value.
|
|
135
|
+
verify: SSL certificate verification. Pass ``False`` to disable
|
|
136
|
+
SSL verification (e.g. for local development).
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
An :class:`~reve._response.ImageResponse` containing the generated
|
|
140
|
+
PIL Image and response metadata (request_id, credits_used, etc.).
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
ReveAuthenticationError: If the API token is invalid (HTTP 401).
|
|
144
|
+
ReveBudgetExhaustedError: If the credit budget is exhausted (HTTP 402).
|
|
145
|
+
ReveRateLimitError: If the rate limit is exceeded (HTTP 429).
|
|
146
|
+
ReveValidationError: If the request parameters are invalid (HTTP 400).
|
|
147
|
+
ReveContentViolationError: If the generated content violates policies.
|
|
148
|
+
ReveAPIError: For other API errors.
|
|
149
|
+
"""
|
|
150
|
+
client = _make_client(api_token, api_url, proxy_authorization, verify=verify)
|
|
151
|
+
body = _build_body(locals())
|
|
152
|
+
raw_bytes, headers = client.post("/v1/image/create/", body, accept="image/jpeg")
|
|
153
|
+
return _parse_image_response(raw_bytes, headers)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def remix(
|
|
157
|
+
prompt: str,
|
|
158
|
+
reference_images: Sequence[ImageInput],
|
|
159
|
+
*,
|
|
160
|
+
aspect_ratio: Optional[str] = None,
|
|
161
|
+
version: Optional[str] = None,
|
|
162
|
+
test_time_scaling: Optional[int] = None,
|
|
163
|
+
postprocessing: Optional[List[Dict[str, Any]]] = None,
|
|
164
|
+
api_token: Optional[str] = None,
|
|
165
|
+
api_url: Optional[str] = None,
|
|
166
|
+
proxy_authorization: Optional[str] = None,
|
|
167
|
+
verify: bool = True,
|
|
168
|
+
) -> ImageResponse:
|
|
169
|
+
"""Generate a new image by remixing reference images with a text prompt.
|
|
170
|
+
|
|
171
|
+
Reference images can be referred to in the prompt using ``<ref>0</ref>``,
|
|
172
|
+
``<ref>1</ref>``, etc.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
prompt: Text prompt describing the desired output. Use
|
|
176
|
+
``<ref>N</ref>`` tags to reference images by index.
|
|
177
|
+
reference_images: Sequence of reference images. Each element can be
|
|
178
|
+
a file path (str), raw bytes, or a PIL Image.
|
|
179
|
+
aspect_ratio: Aspect ratio string (see :func:`create`).
|
|
180
|
+
version: Model version identifier or ``"latest"``.
|
|
181
|
+
test_time_scaling: Quality scaling factor (1–5).
|
|
182
|
+
postprocessing: List of postprocessing operation dicts.
|
|
183
|
+
api_token: Override API token for this request.
|
|
184
|
+
api_url: Override base API URL.
|
|
185
|
+
proxy_authorization: Override proxy-authorization header value.
|
|
186
|
+
verify: SSL certificate verification. Pass ``False`` to disable
|
|
187
|
+
SSL verification (e.g. for local development).
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
An :class:`~reve._response.ImageResponse` containing the generated
|
|
191
|
+
PIL Image and response metadata.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ReveAuthenticationError: If the API token is invalid (HTTP 401).
|
|
195
|
+
ReveBudgetExhaustedError: If the credit budget is exhausted (HTTP 402).
|
|
196
|
+
ReveRateLimitError: If the rate limit is exceeded (HTTP 429).
|
|
197
|
+
ReveValidationError: If the request parameters are invalid (HTTP 400).
|
|
198
|
+
ReveContentViolationError: If the generated content violates policies.
|
|
199
|
+
ReveAPIError: For other API errors.
|
|
200
|
+
TypeError: If a reference image has an unsupported type.
|
|
201
|
+
"""
|
|
202
|
+
client = _make_client(api_token, api_url, proxy_authorization, verify=verify)
|
|
203
|
+
body = _build_body(locals())
|
|
204
|
+
# Encode reference images to base64
|
|
205
|
+
body["reference_images"] = [_encode_image(img) for img in reference_images]
|
|
206
|
+
raw_bytes, headers = client.post("/v1/image/remix/", body, accept="image/jpeg")
|
|
207
|
+
return _parse_image_response(raw_bytes, headers)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def edit(
|
|
211
|
+
edit_instruction: str,
|
|
212
|
+
reference_image: ImageInput,
|
|
213
|
+
*,
|
|
214
|
+
aspect_ratio: Optional[str] = None,
|
|
215
|
+
version: Optional[str] = None,
|
|
216
|
+
test_time_scaling: Optional[int] = None,
|
|
217
|
+
postprocessing: Optional[List[Dict[str, Any]]] = None,
|
|
218
|
+
api_token: Optional[str] = None,
|
|
219
|
+
api_url: Optional[str] = None,
|
|
220
|
+
proxy_authorization: Optional[str] = None,
|
|
221
|
+
verify: bool = True,
|
|
222
|
+
) -> ImageResponse:
|
|
223
|
+
"""Edit an existing image using a natural-language instruction.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
edit_instruction: A description of the edit to apply
|
|
227
|
+
(e.g. ``"Make the sky more dramatic"``).
|
|
228
|
+
reference_image: The source image to edit. Can be a file path (str),
|
|
229
|
+
raw bytes, or a PIL Image.
|
|
230
|
+
aspect_ratio: Aspect ratio string (see :func:`create`).
|
|
231
|
+
version: Model version identifier or ``"latest"``.
|
|
232
|
+
test_time_scaling: Quality scaling factor (1–5).
|
|
233
|
+
postprocessing: List of postprocessing operation dicts.
|
|
234
|
+
api_token: Override API token for this request.
|
|
235
|
+
api_url: Override base API URL.
|
|
236
|
+
proxy_authorization: Override proxy-authorization header value.
|
|
237
|
+
verify: SSL certificate verification. Pass ``False`` to disable
|
|
238
|
+
SSL verification (e.g. for local development).
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
An :class:`~reve._response.ImageResponse` containing the edited
|
|
242
|
+
PIL Image and response metadata.
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
ReveAuthenticationError: If the API token is invalid (HTTP 401).
|
|
246
|
+
ReveBudgetExhaustedError: If the credit budget is exhausted (HTTP 402).
|
|
247
|
+
ReveRateLimitError: If the rate limit is exceeded (HTTP 429).
|
|
248
|
+
ReveValidationError: If the request parameters are invalid (HTTP 400).
|
|
249
|
+
ReveContentViolationError: If the edited content violates policies.
|
|
250
|
+
ReveAPIError: For other API errors.
|
|
251
|
+
TypeError: If the reference image has an unsupported type.
|
|
252
|
+
"""
|
|
253
|
+
client = _make_client(api_token, api_url, proxy_authorization, verify=verify)
|
|
254
|
+
body = _build_body(locals())
|
|
255
|
+
# Encode reference image to base64
|
|
256
|
+
body["reference_image"] = _encode_image(reference_image)
|
|
257
|
+
raw_bytes, headers = client.post("/v1/image/edit/", body, accept="image/jpeg")
|
|
258
|
+
return _parse_image_response(raw_bytes, headers)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def get_balance(
|
|
262
|
+
*,
|
|
263
|
+
api_token: Optional[str] = None,
|
|
264
|
+
api_url: Optional[str] = None,
|
|
265
|
+
proxy_authorization: Optional[str] = None,
|
|
266
|
+
verify: bool = True,
|
|
267
|
+
) -> Dict[str, Any]:
|
|
268
|
+
"""Get the current credit balance.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
api_token: Override API token for this request.
|
|
272
|
+
api_url: Override base API URL.
|
|
273
|
+
proxy_authorization: Override proxy-authorization header value.
|
|
274
|
+
verify: SSL certificate verification. Pass ``False`` to disable
|
|
275
|
+
SSL verification (e.g. for local development).
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
A dict with ``budget_id`` (str) and ``new_balance`` (number) keys.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
ReveAuthenticationError: If the API token is invalid (HTTP 401).
|
|
282
|
+
ReveAPIError: For other API errors.
|
|
283
|
+
"""
|
|
284
|
+
client = _make_client(api_token, api_url, proxy_authorization, verify=verify)
|
|
285
|
+
return client.get("/api/misc/balance/")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def list_effects(
|
|
289
|
+
source: Optional[str] = None,
|
|
290
|
+
*,
|
|
291
|
+
api_token: Optional[str] = None,
|
|
292
|
+
api_url: Optional[str] = None,
|
|
293
|
+
proxy_authorization: Optional[str] = None,
|
|
294
|
+
verify: bool = True,
|
|
295
|
+
) -> List[Dict[str, Any]]:
|
|
296
|
+
"""List available effects for postprocessing.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
source: Filter effects by source. One of ``"all"``, ``"project"``,
|
|
300
|
+
or ``"preset"``. If ``None``, returns all effects.
|
|
301
|
+
api_token: Override API token for this request.
|
|
302
|
+
api_url: Override base API URL.
|
|
303
|
+
proxy_authorization: Override proxy-authorization header value.
|
|
304
|
+
verify: SSL certificate verification. Pass ``False`` to disable
|
|
305
|
+
SSL verification (e.g. for local development).
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
A list of dicts, each with ``name``, ``description``, ``source``,
|
|
309
|
+
and ``category`` keys describing an available effect.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
ReveAuthenticationError: If the API token is invalid (HTTP 401).
|
|
313
|
+
ReveAPIError: For other API errors.
|
|
314
|
+
"""
|
|
315
|
+
client = _make_client(api_token, api_url, proxy_authorization, verify=verify)
|
|
316
|
+
params: Optional[Dict[str, str]] = None
|
|
317
|
+
if source is not None:
|
|
318
|
+
params = {"source": source}
|
|
319
|
+
resp = client.get("/v1/image/effect/", params=params)
|
|
320
|
+
return resp.get("effects", [])
|
|
321
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Postprocessing helper functions for building postprocessing pipelines.
|
|
2
|
+
|
|
3
|
+
These functions return dicts that can be passed in the ``postprocessing``
|
|
4
|
+
list of image generation calls.
|
|
5
|
+
|
|
6
|
+
Example::
|
|
7
|
+
|
|
8
|
+
from reve.v1.postprocessing import upscale, remove_background
|
|
9
|
+
|
|
10
|
+
img = create(
|
|
11
|
+
prompt="A red dragon",
|
|
12
|
+
postprocessing=[upscale(factor=2), remove_background()],
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upscale(factor: int = 2) -> Dict[str, Any]:
|
|
20
|
+
"""Upscale the generated image by a given factor.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
factor: Upscale multiplier (default 2).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A postprocessing operation dict suitable for the ``postprocessing``
|
|
27
|
+
parameter of image generation functions.
|
|
28
|
+
"""
|
|
29
|
+
return {"process": "upscale", "upscale_factor": factor}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def remove_background() -> Dict[str, Any]:
|
|
33
|
+
"""Remove the image background, producing a transparent PNG.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
A postprocessing operation dict suitable for the ``postprocessing``
|
|
37
|
+
parameter of image generation functions.
|
|
38
|
+
"""
|
|
39
|
+
return {"process": "remove_background"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fit_image(
|
|
43
|
+
max_width: Optional[int] = None,
|
|
44
|
+
max_height: Optional[int] = None,
|
|
45
|
+
max_dim: Optional[int] = None,
|
|
46
|
+
) -> Dict[str, Any]:
|
|
47
|
+
"""Constrain image dimensions to fit within given bounds.
|
|
48
|
+
|
|
49
|
+
At least one constraint should be provided.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
max_width: Maximum width in pixels (1–1024).
|
|
53
|
+
max_height: Maximum height in pixels (1–1024).
|
|
54
|
+
max_dim: Maximum dimension for both width and height (1–1024).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A postprocessing operation dict suitable for the ``postprocessing``
|
|
58
|
+
parameter of image generation functions.
|
|
59
|
+
"""
|
|
60
|
+
result: Dict[str, Any] = {"process": "fit_image"}
|
|
61
|
+
if max_width is not None:
|
|
62
|
+
result["max_width"] = max_width
|
|
63
|
+
if max_height is not None:
|
|
64
|
+
result["max_height"] = max_height
|
|
65
|
+
if max_dim is not None:
|
|
66
|
+
result["max_dim"] = max_dim
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def effect(name: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
71
|
+
"""Apply a named effect to the generated image.
|
|
72
|
+
|
|
73
|
+
Use :func:`reve.v1.image.list_effects` to discover available effect names.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
name: Effect name (e.g. ``"sepia"``, ``"blur"``).
|
|
77
|
+
parameters: Optional dict of effect-specific parameters.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A postprocessing operation dict suitable for the ``postprocessing``
|
|
81
|
+
parameter of image generation functions.
|
|
82
|
+
"""
|
|
83
|
+
result: Dict[str, Any] = {"process": "effect", "effect_name": name}
|
|
84
|
+
if parameters is not None:
|
|
85
|
+
result["effect_parameters"] = parameters
|
|
86
|
+
return result
|
|
87
|
+
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reve
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Reve image generation API
|
|
5
|
+
License-Expression: CC-BY-SA-4.0
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.20.0
|
|
9
|
+
Requires-Dist: Pillow>=8.0.0
|
|
10
|
+
Requires-Dist: pydantic>=1.10.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
13
|
+
Requires-Dist: responses>=0.20.0; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# Reve Python SDK
|
|
16
|
+
|
|
17
|
+
A Pythonic interface to the [Reve](https://reve.art) image-generation API.
|
|
18
|
+
Generate, remix, and edit images with a handful of function calls.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
From PyPI:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install reve
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
From source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/reve-ai/reve-core.git
|
|
32
|
+
cd reve-core/sdk/python
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from reve.v1.image import create
|
|
40
|
+
|
|
41
|
+
img = create(prompt="A beautiful sunset over the ocean")
|
|
42
|
+
img.save("sunset.jpg")
|
|
43
|
+
print(img.credits_remaining)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> Set the `REVE_API_TOKEN` environment variable before running,
|
|
47
|
+
> or pass `api_token=` directly to any function.
|
|
48
|
+
|
|
49
|
+
## Authentication
|
|
50
|
+
|
|
51
|
+
The SDK reads credentials from environment variables by default:
|
|
52
|
+
|
|
53
|
+
| Variable | Description | Default |
|
|
54
|
+
|----------|-------------|---------|
|
|
55
|
+
| `REVE_API_TOKEN` | Bearer token (required) | — |
|
|
56
|
+
| `REVE_MONOLITH_LOCATION` | API base URL | `https://api.reve.com` |
|
|
57
|
+
| `REVE_PROXY_AUTHORIZATION` | Proxy-authorization header | — |
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export REVE_API_TOKEN="papi.your-token-here"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can also pass them per-call:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
img = create(
|
|
67
|
+
prompt="A sunset",
|
|
68
|
+
api_token="papi.your-token-here",
|
|
69
|
+
api_url="https://custom-endpoint.example.com",
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
All image functions live in `reve.v1.image`.
|
|
76
|
+
|
|
77
|
+
### `create(prompt, *, aspect_ratio, version, test_time_scaling, postprocessing, ...)`
|
|
78
|
+
|
|
79
|
+
Generate an image from a text prompt.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from reve.v1.image import create
|
|
83
|
+
from reve.v1.postprocessing import upscale, remove_background
|
|
84
|
+
|
|
85
|
+
img = create(
|
|
86
|
+
prompt="A red dragon flying over mountains",
|
|
87
|
+
aspect_ratio="16:9",
|
|
88
|
+
version="latest",
|
|
89
|
+
test_time_scaling=3,
|
|
90
|
+
postprocessing=[upscale(factor=2), remove_background()],
|
|
91
|
+
)
|
|
92
|
+
img.save("dragon.png")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Parameter | Type | Description |
|
|
96
|
+
|-----------|------|-------------|
|
|
97
|
+
| `prompt` | `str` | Text description of the image (positional). |
|
|
98
|
+
| `aspect_ratio` | `str \| None` | One of `"16:9"`, `"3:2"`, `"4:3"`, `"1:1"`, `"3:4"`, `"2:3"`, `"9:16"`, `"auto"`. Default `"auto"`. |
|
|
99
|
+
| `version` | `str \| None` | Model version identifier or `"latest"`. |
|
|
100
|
+
| `test_time_scaling` | `int \| None` | Quality factor 1–5. Higher = better quality, more credits. |
|
|
101
|
+
| `postprocessing` | `list[dict] \| None` | Postprocessing pipeline (see below). |
|
|
102
|
+
|
|
103
|
+
### `remix(prompt, reference_images, *, ...)`
|
|
104
|
+
|
|
105
|
+
Remix reference images into a new image guided by a prompt.
|
|
106
|
+
Use `<ref>0</ref>`, `<ref>1</ref>`, … to refer to each reference image.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from reve.v1.image import remix
|
|
110
|
+
|
|
111
|
+
img = remix(
|
|
112
|
+
prompt="The subject from <ref>0</ref> standing in a magical forest",
|
|
113
|
+
reference_images=["photo.jpg"],
|
|
114
|
+
aspect_ratio="1:1",
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Parameter | Type | Description |
|
|
119
|
+
|-----------|------|-------------|
|
|
120
|
+
| `prompt` | `str` | Text prompt with optional `<ref>N</ref>` tags (positional). |
|
|
121
|
+
| `reference_images` | `Sequence[str \| bytes \| PIL.Image]` | Reference images — file paths, raw bytes, or PIL Images (positional). |
|
|
122
|
+
| `aspect_ratio` | `str \| None` | Aspect ratio (see `create`). |
|
|
123
|
+
| `version` | `str \| None` | Model version. |
|
|
124
|
+
| `test_time_scaling` | `int \| None` | Quality factor 1–5. |
|
|
125
|
+
| `postprocessing` | `list[dict] \| None` | Postprocessing pipeline. |
|
|
126
|
+
|
|
127
|
+
### `edit(edit_instruction, reference_image, *, ...)`
|
|
128
|
+
|
|
129
|
+
Edit an existing image with a natural-language instruction.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from reve.v1.image import edit
|
|
133
|
+
|
|
134
|
+
img = edit(
|
|
135
|
+
edit_instruction="Make the sky more dramatic with storm clouds",
|
|
136
|
+
reference_image="original.jpg",
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Parameter | Type | Description |
|
|
141
|
+
|-----------|------|-------------|
|
|
142
|
+
| `edit_instruction` | `str` | Description of the edit (positional). |
|
|
143
|
+
| `reference_image` | `str \| bytes \| PIL.Image` | Source image (positional). |
|
|
144
|
+
| `aspect_ratio` | `str \| None` | Aspect ratio (see `create`). |
|
|
145
|
+
| `version` | `str \| None` | Model version. |
|
|
146
|
+
| `test_time_scaling` | `int \| None` | Quality factor 1–5. |
|
|
147
|
+
| `postprocessing` | `list[dict] \| None` | Postprocessing pipeline. |
|
|
148
|
+
|
|
149
|
+
### `get_balance(*, ...)`
|
|
150
|
+
|
|
151
|
+
Return the current credit balance.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from reve.v1.image import get_balance
|
|
155
|
+
|
|
156
|
+
balance = get_balance()
|
|
157
|
+
print(balance) # {"budget_id": "abc123", "new_balance": 500}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Returns a `dict` with keys `budget_id` (str) and `new_balance` (number).
|
|
161
|
+
|
|
162
|
+
### `list_effects(source=None, *, ...)`
|
|
163
|
+
|
|
164
|
+
List available effects for postprocessing.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from reve.v1.image import list_effects
|
|
168
|
+
|
|
169
|
+
effects = list_effects(source="preset")
|
|
170
|
+
for e in effects:
|
|
171
|
+
print(e["name"], "-", e["description"])
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Parameter | Type | Description |
|
|
175
|
+
|-----------|------|-------------|
|
|
176
|
+
| `source` | `str \| None` | Filter by source: `"all"`, `"project"`, or `"preset"`. |
|
|
177
|
+
|
|
178
|
+
Returns a list of dicts with `name`, `description`, `source`, and `category` keys.
|
|
179
|
+
|
|
180
|
+
## Postprocessing
|
|
181
|
+
|
|
182
|
+
Build postprocessing pipelines with helpers from `reve.v1.postprocessing`:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from reve.v1.postprocessing import upscale, remove_background, fit_image, effect
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
| Helper | Description |
|
|
189
|
+
|--------|-------------|
|
|
190
|
+
| `upscale(factor=2)` | Upscale the image by the given factor. |
|
|
191
|
+
| `remove_background()` | Remove the background (produces transparent PNG). |
|
|
192
|
+
| `fit_image(max_width=None, max_height=None, max_dim=None)` | Constrain dimensions (pixels, 1–1024). |
|
|
193
|
+
| `effect(name, parameters=None)` | Apply a named effect. Use `list_effects()` for available names. |
|
|
194
|
+
|
|
195
|
+
Pass them as a list to the `postprocessing` parameter:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
img = create(
|
|
199
|
+
prompt="A cat astronaut",
|
|
200
|
+
postprocessing=[upscale(factor=2), remove_background()],
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Response Object
|
|
205
|
+
|
|
206
|
+
`create()`, `remix()`, and `edit()` return an `ImageResponse` (a Pydantic BaseModel):
|
|
207
|
+
|
|
208
|
+
| Field | Type | Description |
|
|
209
|
+
|-------|------|-------------|
|
|
210
|
+
| `image` | `PIL.Image.Image` | The generated image. |
|
|
211
|
+
| `request_id` | `str \| None` | Unique request identifier. |
|
|
212
|
+
| `credits_used` | `int \| None` | Credits consumed by this request. |
|
|
213
|
+
| `credits_remaining` | `int \| None` | Credits remaining in the budget. |
|
|
214
|
+
| `version` | `str \| None` | Model version used. |
|
|
215
|
+
| `content_violation` | `bool` | Whether a content violation was flagged. |
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
img = create(prompt="A sunset")
|
|
219
|
+
img.image # PIL.Image.Image
|
|
220
|
+
img.request_id # "req_abc123"
|
|
221
|
+
img.credits_used # 10
|
|
222
|
+
img.credits_remaining # 490
|
|
223
|
+
img.version # "v1.2"
|
|
224
|
+
img.save("out.jpg") # delegates to PIL.Image.save()
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Error Handling
|
|
228
|
+
|
|
229
|
+
All exceptions inherit from `ReveAPIError`:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
ReveAPIError # Base — any API error
|
|
233
|
+
├── ReveAuthenticationError # HTTP 401 — bad or missing token
|
|
234
|
+
├── ReveBudgetExhaustedError # HTTP 402 — out of credits
|
|
235
|
+
├── ReveRateLimitError # HTTP 429 — rate limited (has .retry_after)
|
|
236
|
+
├── ReveValidationError # HTTP 400 — invalid parameters
|
|
237
|
+
└── ReveContentViolationError # Content policy violation
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from reve.exceptions import ReveAPIError, ReveRateLimitError
|
|
242
|
+
from reve.v1.image import create
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
img = create(prompt="A sunset")
|
|
246
|
+
except ReveRateLimitError as exc:
|
|
247
|
+
print(f"Rate limited — retry after {exc.retry_after}s")
|
|
248
|
+
except ReveAPIError as exc:
|
|
249
|
+
print(f"API error (status {exc.status_code}): {exc.message}")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Examples
|
|
253
|
+
|
|
254
|
+
Working example scripts are in the [`examples/`](examples/) directory:
|
|
255
|
+
|
|
256
|
+
- [`create_image.py`](examples/create_image.py) — Generate images with optional postprocessing.
|
|
257
|
+
- [`remix_image.py`](examples/remix_image.py) — Remix a reference image with a prompt.
|
|
258
|
+
- [`edit_image.py`](examples/edit_image.py) — Edit an existing image.
|
|
259
|
+
|
|
260
|
+
## Development
|
|
261
|
+
|
|
262
|
+
Install development dependencies:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pip install -e ".[dev]"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Run the test suite:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
pytest
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
This SDK is released under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
reve/__init__.py,sha256=SDa-YQGjlx3yWXw3ps4cNe7P00Fs7M_zayGUdhB2AEw,105
|
|
2
|
+
reve/_client.py,sha256=7RhPAXRFZc1wZW36fwx_cXbnOdc0ki2CHCnP8D1oz1s,5927
|
|
3
|
+
reve/_response.py,sha256=YbaENxOS2ZLzHJehZEzj4t8h40LYv6sRq8QH3TFvyIA,2856
|
|
4
|
+
reve/exceptions.py,sha256=YgtOX9oL4YeHOfHIWa93uWhim9UOooQA8qCXhIOUbUY,3394
|
|
5
|
+
reve/v1/__init__.py,sha256=KSCzZtSzU7QPunuKVfAEuLt-hcgM_DL8bD8pfsoXE6o,27
|
|
6
|
+
reve/v1/image.py,sha256=b0apa2Du3PFm9Eiq26p2XjaqzUsnd6iZBzL38AUvyIs,12355
|
|
7
|
+
reve/v1/postprocessing.py,sha256=48GZbTRr6iMRO5llr3G2rCaI80MiOXbuQ7wYZQMZyI0,2648
|
|
8
|
+
reve-0.1.0.dist-info/METADATA,sha256=cYhDV5x-BFzC59lSPL8Kf3UtZaT6m9fzFbZYTTeCA5s,8163
|
|
9
|
+
reve-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
10
|
+
reve-0.1.0.dist-info/top_level.txt,sha256=u_WgbUwgdd-a5Zq9cqf9RDMEu1N24r4kpbEkKsxUPeg,5
|
|
11
|
+
reve-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
reve
|