runapi-recraft 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ # Build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ *.egg
6
+
7
+ # Bytecode
8
+ __pycache__/
9
+ *.py[cod]
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+
15
+ # uv
16
+ uv.lock
17
+
18
+ # Test / type caches
19
+ .pytest_cache/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .coverage
23
+ htmlcov/
24
+
25
+ # IDE / OS
26
+ .idea/
27
+ .vscode/
28
+ *.swp
29
+ .DS_Store
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: runapi-recraft
3
+ Version: 0.1.0
4
+ Summary: Recraft image upscale and background-removal client for RunAPI
5
+ Project-URL: Homepage, https://runapi.ai/models/recraft
6
+ Project-URL: Documentation, https://runapi.ai/docs#sdk-recraft
7
+ Author-email: RunAPI <contact@runapi.ai>
8
+ License-Expression: Apache-2.0
9
+ Keywords: ai,recraft,remove-background,runapi,sdk,upscale-image
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: runapi-core
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Recraft Python SDK for RunAPI
15
+
16
+ The Recraft Python SDK is the language-specific package for Recraft on RunAPI.
17
+ Use it for image upscale and background-removal flows when your application needs
18
+ JSON request bodies, task status lookup, and consistent RunAPI errors in Python.
19
+
20
+ For model details, use https://runapi.ai/models/recraft; for API reference, use
21
+ https://runapi.ai/docs#recraft; for SDK docs, use https://runapi.ai/docs#sdk-recraft.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install runapi-recraft
27
+ ```
28
+
29
+ ## Quick start
30
+
31
+ ```python
32
+ from runapi.recraft import RecraftClient
33
+
34
+ client = RecraftClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
35
+
36
+ task = client.upscale_image.create(
37
+ model="recraft-crisp-upscale",
38
+ source_image_url="https://example.com/source.jpg",
39
+ )
40
+ status = client.upscale_image.get(task.id)
41
+
42
+ removed = client.remove_background.create(
43
+ model="recraft-remove-background",
44
+ source_image_url="https://example.com/source.jpg",
45
+ )
46
+ ```
47
+
48
+ Use `create` to submit a task and return quickly, `get` to fetch the latest task
49
+ state, and `run` to create and poll until completion:
50
+
51
+ ```python
52
+ result = client.upscale_image.run(
53
+ model="recraft-crisp-upscale",
54
+ source_image_url="https://example.com/source.jpg",
55
+ )
56
+ print(result.images[0].url)
57
+ ```
58
+
59
+ In web request handlers, prefer `create` plus webhook or later `get` polling so a
60
+ worker is not held open.
61
+
62
+ RunAPI-generated file URLs are temporary. Download and store generated images in
63
+ your own durable storage within 7 days; do not treat returned URLs as long-term
64
+ assets.
65
+
66
+ ## Language notes
67
+
68
+ Pass parameters as keyword arguments and catch the `runapi.recraft` error
69
+ classes when building image jobs or scripts. The available resources are
70
+ `upscale_image` and `remove_background`. Keep `RUNAPI_API_KEY` in the environment
71
+ or your secret manager; never commit API keys or callback secrets.
72
+
73
+ ## Links
74
+
75
+ - Model page: https://runapi.ai/models/recraft
76
+ - SDK docs: https://runapi.ai/docs#sdk-recraft
77
+ - Product docs: https://runapi.ai/docs#recraft
78
+ - Pricing and rate limits: https://runapi.ai/models/recraft
79
+ - Full catalog: https://runapi.ai/models
80
+
81
+ ## License
82
+
83
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,70 @@
1
+ # Recraft Python SDK for RunAPI
2
+
3
+ The Recraft Python SDK is the language-specific package for Recraft on RunAPI.
4
+ Use it for image upscale and background-removal flows when your application needs
5
+ JSON request bodies, task status lookup, and consistent RunAPI errors in Python.
6
+
7
+ For model details, use https://runapi.ai/models/recraft; for API reference, use
8
+ https://runapi.ai/docs#recraft; for SDK docs, use https://runapi.ai/docs#sdk-recraft.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install runapi-recraft
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```python
19
+ from runapi.recraft import RecraftClient
20
+
21
+ client = RecraftClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
22
+
23
+ task = client.upscale_image.create(
24
+ model="recraft-crisp-upscale",
25
+ source_image_url="https://example.com/source.jpg",
26
+ )
27
+ status = client.upscale_image.get(task.id)
28
+
29
+ removed = client.remove_background.create(
30
+ model="recraft-remove-background",
31
+ source_image_url="https://example.com/source.jpg",
32
+ )
33
+ ```
34
+
35
+ Use `create` to submit a task and return quickly, `get` to fetch the latest task
36
+ state, and `run` to create and poll until completion:
37
+
38
+ ```python
39
+ result = client.upscale_image.run(
40
+ model="recraft-crisp-upscale",
41
+ source_image_url="https://example.com/source.jpg",
42
+ )
43
+ print(result.images[0].url)
44
+ ```
45
+
46
+ In web request handlers, prefer `create` plus webhook or later `get` polling so a
47
+ worker is not held open.
48
+
49
+ RunAPI-generated file URLs are temporary. Download and store generated images in
50
+ your own durable storage within 7 days; do not treat returned URLs as long-term
51
+ assets.
52
+
53
+ ## Language notes
54
+
55
+ Pass parameters as keyword arguments and catch the `runapi.recraft` error
56
+ classes when building image jobs or scripts. The available resources are
57
+ `upscale_image` and `remove_background`. Keep `RUNAPI_API_KEY` in the environment
58
+ or your secret manager; never commit API keys or callback secrets.
59
+
60
+ ## Links
61
+
62
+ - Model page: https://runapi.ai/models/recraft
63
+ - SDK docs: https://runapi.ai/docs#sdk-recraft
64
+ - Product docs: https://runapi.ai/docs#recraft
65
+ - Pricing and rate limits: https://runapi.ai/models/recraft
66
+ - Full catalog: https://runapi.ai/models
67
+
68
+ ## License
69
+
70
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "runapi-recraft"
7
+ version = "0.1.0"
8
+ description = "Recraft image upscale and background-removal client for RunAPI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
13
+ keywords = ["runapi", "recraft", "upscale-image", "remove-background", "ai", "sdk"]
14
+ dependencies = ["runapi-core"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://runapi.ai/models/recraft"
18
+ Documentation = "https://runapi.ai/docs#sdk-recraft"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/runapi"]
22
+
23
+ [tool.uv]
24
+ package = true
25
+
26
+ [tool.uv.sources]
27
+ runapi-core = { workspace = true }
28
+
29
+ [dependency-groups]
30
+ dev = ["pytest>=8"]
31
+
32
+ [tool.runapi]
33
+ slug = "recraft"
@@ -0,0 +1,24 @@
1
+ """Recraft client for RunAPI."""
2
+
3
+ from runapi.core import (
4
+ AuthenticationError,
5
+ InsufficientCreditsError,
6
+ NotFoundError,
7
+ RateLimitError,
8
+ TaskFailedError,
9
+ TaskTimeoutError,
10
+ ValidationError,
11
+ )
12
+
13
+ from .client import RecraftClient
14
+
15
+ __all__ = [
16
+ "RecraftClient",
17
+ "AuthenticationError",
18
+ "RateLimitError",
19
+ "InsufficientCreditsError",
20
+ "NotFoundError",
21
+ "ValidationError",
22
+ "TaskFailedError",
23
+ "TaskTimeoutError",
24
+ ]
@@ -0,0 +1,30 @@
1
+ """Recraft client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from runapi.core import ClientOptions, HttpClient, resolve_api_key
8
+
9
+ from .resources.remove_background import RemoveBackground
10
+ from .resources.upscale_image import UpscaleImage
11
+
12
+
13
+ class RecraftClient:
14
+ """Recraft image upscale and background-removal client.
15
+
16
+ Example::
17
+
18
+ client = RecraftClient(api_key="sk-...")
19
+ result = client.upscale_image.run(
20
+ model="recraft-crisp-upscale",
21
+ source_image_url="https://example.com/source.jpg",
22
+ )
23
+ """
24
+
25
+ def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
26
+ resolved_api_key = resolve_api_key(api_key)
27
+ client_options = ClientOptions(api_key=resolved_api_key, **options)
28
+ http = client_options.http_client or HttpClient(client_options)
29
+ self.upscale_image = UpscaleImage(http)
30
+ self.remove_background = RemoveBackground(http)
@@ -0,0 +1,22 @@
1
+ CONTRACT = {
2
+ "remove-background": {
3
+ "models": ["recraft-remove-background"],
4
+ "fields_by_model": {
5
+ "recraft-remove-background": {
6
+ "source_image_url": {
7
+ "required": True
8
+ }
9
+ }
10
+ }
11
+ },
12
+ "upscale-image": {
13
+ "models": ["recraft-crisp-upscale"],
14
+ "fields_by_model": {
15
+ "recraft-crisp-upscale": {
16
+ "source_image_url": {
17
+ "required": True
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ from .remove_background import RemoveBackground
2
+ from .upscale_image import UpscaleImage
3
+
4
+ __all__ = ["UpscaleImage", "RemoveBackground"]
@@ -0,0 +1,58 @@
1
+ """Recraft remove-background resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from runapi.core import Resource
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import (
11
+ CompletedImageTaskResponse,
12
+ ImageTaskResponse,
13
+ )
14
+
15
+
16
+ class RemoveBackground(Resource):
17
+ """Remove image backgrounds with Recraft models."""
18
+
19
+ ENDPOINT = "/api/v1/recraft/remove_background"
20
+
21
+ RESPONSE_CLASS = ImageTaskResponse
22
+ COMPLETED_RESPONSE_CLASS = CompletedImageTaskResponse
23
+
24
+ def run(self, **params: Any) -> Any:
25
+ """Run a remove-background task and poll until it completes.
26
+
27
+ Args:
28
+ **params: remove-background parameters (model, ...).
29
+
30
+ Returns:
31
+ The completed (narrowed) remove-background response.
32
+ """
33
+ task = self.create(**params)
34
+ return self._poll_until_complete(lambda: self.get(task.id))
35
+
36
+ def create(self, **params: Any) -> Any:
37
+ """Create a remove-background task and return immediately with an id.
38
+
39
+ Args:
40
+ **params: remove-background parameters (model, ...).
41
+
42
+ Returns:
43
+ The task creation result with an id.
44
+ """
45
+ compacted = self._compact_params(params)
46
+ self._validate_contract(CONTRACT["remove-background"], compacted)
47
+ return self._request("post", self.ENDPOINT, body=compacted)
48
+
49
+ def get(self, id: str) -> Any:
50
+ """Fetch the current status of a remove-background task.
51
+
52
+ Args:
53
+ id: The task id returned by ``create``.
54
+
55
+ Returns:
56
+ The current task status.
57
+ """
58
+ return self._request("get", f"{self.ENDPOINT}/{id}")
@@ -0,0 +1,58 @@
1
+ """Recraft upscale-image resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from runapi.core import Resource
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import (
11
+ CompletedImageTaskResponse,
12
+ ImageTaskResponse,
13
+ )
14
+
15
+
16
+ class UpscaleImage(Resource):
17
+ """Upscale images with Recraft models."""
18
+
19
+ ENDPOINT = "/api/v1/recraft/upscale_image"
20
+
21
+ RESPONSE_CLASS = ImageTaskResponse
22
+ COMPLETED_RESPONSE_CLASS = CompletedImageTaskResponse
23
+
24
+ def run(self, **params: Any) -> Any:
25
+ """Run an image upscale task and poll until it completes.
26
+
27
+ Args:
28
+ **params: image upscale parameters (model, ...).
29
+
30
+ Returns:
31
+ The completed (narrowed) image upscale response.
32
+ """
33
+ task = self.create(**params)
34
+ return self._poll_until_complete(lambda: self.get(task.id))
35
+
36
+ def create(self, **params: Any) -> Any:
37
+ """Create an image upscale task and return immediately with an id.
38
+
39
+ Args:
40
+ **params: image upscale parameters (model, ...).
41
+
42
+ Returns:
43
+ The task creation result with an id.
44
+ """
45
+ compacted = self._compact_params(params)
46
+ self._validate_contract(CONTRACT["upscale-image"], compacted)
47
+ return self._request("post", self.ENDPOINT, body=compacted)
48
+
49
+ def get(self, id: str) -> Any:
50
+ """Fetch the current status of an image upscale task.
51
+
52
+ Args:
53
+ id: The task id returned by ``create``.
54
+
55
+ Returns:
56
+ The current task status.
57
+ """
58
+ return self._request("get", f"{self.ENDPOINT}/{id}")
@@ -0,0 +1,24 @@
1
+ """Recraft model lists and response models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from runapi.core import BaseModel, TaskResponse, optional, required
6
+
7
+
8
+ class Image(BaseModel):
9
+ url = optional(str)
10
+
11
+
12
+ class ImageTaskResponse(TaskResponse):
13
+ """Recraft image task status response."""
14
+
15
+ id = required(str)
16
+ status = optional(str, enum=lambda: TaskResponse.Status.ALL)
17
+ images = optional([lambda: Image])
18
+ error = optional(str)
19
+
20
+
21
+ class CompletedImageTaskResponse(ImageTaskResponse):
22
+ """Narrowed response from ``run()`` once polling observes completion."""
23
+
24
+ images = required([lambda: Image])
@@ -0,0 +1,156 @@
1
+ import pytest
2
+
3
+ from runapi.core import config
4
+ from runapi.core.errors import AuthenticationError, ValidationError
5
+ from runapi.recraft import RecraftClient
6
+ from runapi.recraft.resources.remove_background import RemoveBackground
7
+ from runapi.recraft.resources.upscale_image import UpscaleImage
8
+ from runapi.recraft.types import CompletedImageTaskResponse, ImageTaskResponse
9
+
10
+
11
+ class FakeHttp:
12
+ def __init__(self, *responses):
13
+ self._responses = list(responses)
14
+ self.calls = []
15
+
16
+ def request(self, method, path, body=None, options=None):
17
+ self.calls.append((method, path, body))
18
+ if self._responses:
19
+ return self._responses.pop(0)
20
+ return {"id": "task_1", "status": "pending"}
21
+
22
+
23
+ @pytest.fixture(autouse=True)
24
+ def reset_config(monkeypatch):
25
+ monkeypatch.delenv("RUNAPI_API_KEY", raising=False)
26
+ monkeypatch.setattr(config, "api_key", None)
27
+ yield
28
+
29
+
30
+ # --- auth -----------------------------------------------------------------
31
+
32
+
33
+ def test_accepts_api_key_parameter():
34
+ assert isinstance(RecraftClient(api_key="k", http_client=FakeHttp()), RecraftClient)
35
+
36
+
37
+ def test_falls_back_to_global(monkeypatch):
38
+ monkeypatch.setattr(config, "api_key", "global-key")
39
+ assert isinstance(RecraftClient(http_client=FakeHttp()), RecraftClient)
40
+
41
+
42
+ def test_falls_back_to_env(monkeypatch):
43
+ monkeypatch.setenv("RUNAPI_API_KEY", "env-key")
44
+ assert isinstance(RecraftClient(http_client=FakeHttp()), RecraftClient)
45
+
46
+
47
+ def test_raises_without_api_key():
48
+ with pytest.raises(AuthenticationError, match="API key is required"):
49
+ RecraftClient()
50
+
51
+
52
+ # --- injection / accessors ------------------------------------------------
53
+
54
+
55
+ def test_uses_injected_http_client():
56
+ fake = FakeHttp()
57
+ client = RecraftClient(api_key="k", http_client=fake)
58
+ assert client.upscale_image._http is fake
59
+ assert client.remove_background._http is fake
60
+
61
+
62
+ def test_exposes_resource_accessors():
63
+ client = RecraftClient(api_key="k", http_client=FakeHttp())
64
+ assert isinstance(client.upscale_image, UpscaleImage)
65
+ assert isinstance(client.remove_background, RemoveBackground)
66
+
67
+
68
+ # --- request shapes -------------------------------------------------------
69
+
70
+
71
+ def test_upscale_create_posts_compacted_body():
72
+ fake = FakeHttp({"id": "t1", "status": "pending"})
73
+ client = RecraftClient(api_key="k", http_client=fake)
74
+ result = client.upscale_image.create(
75
+ model="recraft-crisp-upscale",
76
+ source_image_url="https://x/a.png",
77
+ callback_url=None,
78
+ )
79
+ assert fake.calls == [
80
+ (
81
+ "post",
82
+ "/api/v1/recraft/upscale_image",
83
+ {"model": "recraft-crisp-upscale", "source_image_url": "https://x/a.png"},
84
+ ),
85
+ ]
86
+ assert isinstance(result, ImageTaskResponse)
87
+
88
+
89
+ def test_upscale_get_fetches_by_id():
90
+ fake = FakeHttp({"id": "t1", "status": "processing"})
91
+ client = RecraftClient(api_key="k", http_client=fake)
92
+ client.upscale_image.get("t1")
93
+ assert fake.calls == [("get", "/api/v1/recraft/upscale_image/t1", None)]
94
+
95
+
96
+ def test_remove_background_create_posts_compacted_body():
97
+ fake = FakeHttp({"id": "t1", "status": "pending"})
98
+ client = RecraftClient(api_key="k", http_client=fake)
99
+ client.remove_background.create(
100
+ model="recraft-remove-background",
101
+ source_image_url="https://x/a.png",
102
+ )
103
+ assert fake.calls == [
104
+ (
105
+ "post",
106
+ "/api/v1/recraft/remove_background",
107
+ {"model": "recraft-remove-background", "source_image_url": "https://x/a.png"},
108
+ ),
109
+ ]
110
+
111
+
112
+ def test_remove_background_get_fetches_by_id():
113
+ fake = FakeHttp({"id": "t1", "status": "processing"})
114
+ client = RecraftClient(api_key="k", http_client=fake)
115
+ client.remove_background.get("t1")
116
+ assert fake.calls == [("get", "/api/v1/recraft/remove_background/t1", None)]
117
+
118
+
119
+ def test_run_narrows_completed_type():
120
+ fake = FakeHttp(
121
+ {"id": "t1", "status": "pending"},
122
+ {"id": "t1", "status": "completed", "images": [{"url": "https://x/y.png"}]},
123
+ )
124
+ client = RecraftClient(api_key="k", http_client=fake)
125
+ result = client.upscale_image.run(
126
+ model="recraft-crisp-upscale", source_image_url="https://x/a.png"
127
+ )
128
+ assert isinstance(result, CompletedImageTaskResponse)
129
+ assert result.images[0].url == "https://x/y.png"
130
+
131
+
132
+ # --- validation -----------------------------------------------------------
133
+
134
+
135
+ def test_upscale_requires_model():
136
+ client = RecraftClient(api_key="k", http_client=FakeHttp())
137
+ with pytest.raises(ValidationError, match="model must be one of: recraft-crisp-upscale"):
138
+ client.upscale_image.create(source_image_url="https://x/a.png")
139
+
140
+
141
+ def test_upscale_requires_source_image_url():
142
+ client = RecraftClient(api_key="k", http_client=FakeHttp())
143
+ with pytest.raises(ValidationError, match="source_image_url is required"):
144
+ client.upscale_image.create(model="recraft-crisp-upscale")
145
+
146
+
147
+ def test_upscale_rejects_unknown_model():
148
+ client = RecraftClient(api_key="k", http_client=FakeHttp())
149
+ with pytest.raises(ValidationError, match="model must be one of: recraft-crisp-upscale"):
150
+ client.upscale_image.create(model="nope", source_image_url="https://x/a.png")
151
+
152
+
153
+ def test_remove_background_rejects_unknown_model():
154
+ client = RecraftClient(api_key="k", http_client=FakeHttp())
155
+ with pytest.raises(ValidationError, match="model must be one of: recraft-remove-background"):
156
+ client.remove_background.create(model="nope", source_image_url="https://x/a.png")