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 ADDED
@@ -0,0 +1,4 @@
1
+ """Reve Python SDK — A Pythonic interface to the Reve image generation API."""
2
+
3
+ __version__ = "0.1.0"
4
+
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
@@ -0,0 +1,2 @@
1
+ """Reve API v1 module."""
2
+
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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ reve