diffusr 0.1.0__py3-none-any.whl → 0.2.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.
- diffusr/__init__.py +4 -1
- diffusr/_client.py +29 -4
- diffusr/_config.py +29 -0
- {diffusr-0.1.0.dist-info → diffusr-0.2.0.dist-info}/METADATA +24 -2
- diffusr-0.2.0.dist-info/RECORD +10 -0
- diffusr-0.1.0.dist-info/RECORD +0 -9
- {diffusr-0.1.0.dist-info → diffusr-0.2.0.dist-info}/WHEEL +0 -0
- {diffusr-0.1.0.dist-info → diffusr-0.2.0.dist-info}/licenses/LICENSE +0 -0
diffusr/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from ._client import DEFAULT_BASE_URL, TERMINAL_STATUSES, Diffusr
|
|
9
|
+
from ._config import config_path, read_config
|
|
9
10
|
from ._errors import DiffusrError
|
|
10
11
|
from ._verify import SIGNATURE_TOLERANCE_SEC, verify_callback
|
|
11
12
|
|
|
@@ -13,8 +14,10 @@ __all__ = [
|
|
|
13
14
|
"Diffusr",
|
|
14
15
|
"DiffusrError",
|
|
15
16
|
"verify_callback",
|
|
17
|
+
"config_path",
|
|
18
|
+
"read_config",
|
|
16
19
|
"DEFAULT_BASE_URL",
|
|
17
20
|
"TERMINAL_STATUSES",
|
|
18
21
|
"SIGNATURE_TOLERANCE_SEC",
|
|
19
22
|
]
|
|
20
|
-
__version__ = "0.
|
|
23
|
+
__version__ = "0.2.0"
|
diffusr/_client.py
CHANGED
|
@@ -11,11 +11,12 @@ import urllib.request
|
|
|
11
11
|
import uuid
|
|
12
12
|
from typing import Any, Callable, Dict, Mapping, Optional, Union
|
|
13
13
|
|
|
14
|
+
from ._config import config_path, read_config # noqa: F401
|
|
14
15
|
from ._errors import DiffusrError
|
|
15
16
|
from ._verify import verify_callback as _verify_callback
|
|
16
17
|
|
|
17
18
|
DEFAULT_BASE_URL = "https://app.diffusr.ai/api/v2"
|
|
18
|
-
SDK_VERSION = "0.
|
|
19
|
+
SDK_VERSION = "0.2.0"
|
|
19
20
|
TERMINAL_STATUSES = ("SUCCEEDED", "FAILED", "CANCELED")
|
|
20
21
|
# Never sleep longer than this on one automatic retry; raise instead.
|
|
21
22
|
MAX_AUTO_RETRY_WAIT_SEC = 30
|
|
@@ -30,7 +31,10 @@ _CONTENT_TYPES = {
|
|
|
30
31
|
class Diffusr:
|
|
31
32
|
"""Client for the diffusr REST API (v2). Bodies and answers are the API's own snake_case JSON.
|
|
32
33
|
|
|
33
|
-
api_key dfsr_live_... or dfsr_test_... (free). Default: the DIFFUSR_KEY environment variable
|
|
34
|
+
api_key dfsr_live_... or dfsr_test_... (free). Default: the DIFFUSR_KEY environment variable,
|
|
35
|
+
then the key `npx diffusr login` saved (see config_path()).
|
|
36
|
+
base_url Default: DIFFUSR_BASE_URL, then the saved file's base_url (only when the key came
|
|
37
|
+
from the file), then https://app.diffusr.ai/api/v2.
|
|
34
38
|
"""
|
|
35
39
|
|
|
36
40
|
def __init__(
|
|
@@ -43,10 +47,16 @@ class Diffusr:
|
|
|
43
47
|
timeout: float = 60.0,
|
|
44
48
|
) -> None:
|
|
45
49
|
key = api_key or os.environ.get("DIFFUSR_KEY")
|
|
50
|
+
file_base: Optional[str] = None
|
|
46
51
|
if not key:
|
|
47
|
-
|
|
52
|
+
saved = read_config()
|
|
53
|
+
if saved:
|
|
54
|
+
key = saved["key"]
|
|
55
|
+
file_base = saved.get("base_url") or None
|
|
56
|
+
if not key:
|
|
57
|
+
raise DiffusrError(0, "MISSING_API_KEY", "Pass an API key, set DIFFUSR_KEY, or run `npx diffusr login`.")
|
|
48
58
|
self._key = key
|
|
49
|
-
self._base = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
59
|
+
self._base = (base_url or os.environ.get("DIFFUSR_BASE_URL") or file_base or DEFAULT_BASE_URL).rstrip("/")
|
|
50
60
|
self._max_retries = max_retries
|
|
51
61
|
self._retry_base = retry_base
|
|
52
62
|
self._timeout = timeout
|
|
@@ -191,6 +201,21 @@ class Diffusr:
|
|
|
191
201
|
def rotate_callback_secret(self) -> Dict[str, Any]:
|
|
192
202
|
return self._request("POST", "/callbacks/secret/rotate", body={})
|
|
193
203
|
|
|
204
|
+
def call(
|
|
205
|
+
self,
|
|
206
|
+
method: str,
|
|
207
|
+
path: str,
|
|
208
|
+
query: Optional[Mapping[str, Any]] = None,
|
|
209
|
+
body: Optional[Dict[str, Any]] = None,
|
|
210
|
+
headers: Optional[Dict[str, str]] = None,
|
|
211
|
+
) -> Dict[str, Any]:
|
|
212
|
+
"""Call any API path the typed methods do not cover, e.g. call("GET", "/me").
|
|
213
|
+
Same retries and errors as every other call. Add your own Idempotency-Key
|
|
214
|
+
header for a make call."""
|
|
215
|
+
q = {k: (str(v).lower() if isinstance(v, bool) else v) for k, v in (query or {}).items() if v is not None}
|
|
216
|
+
suffix = f"?{urllib.parse.urlencode(q)}" if q else ""
|
|
217
|
+
return self._request(method, path + suffix, body=body, headers=headers)
|
|
218
|
+
|
|
194
219
|
@staticmethod
|
|
195
220
|
def verify_callback(
|
|
196
221
|
raw_body: Union[str, bytes],
|
diffusr/_config.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""The key `npx diffusr login` saves, so Python code can use it too."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def config_path() -> str:
|
|
12
|
+
"""$XDG_CONFIG_HOME/diffusr/config.json, else ~/.config/diffusr/config.json; on Windows %APPDATA%\\diffusr\\config.json."""
|
|
13
|
+
if sys.platform == "win32":
|
|
14
|
+
base = os.environ.get("APPDATA") or os.path.join(os.path.expanduser("~"), "AppData", "Roaming")
|
|
15
|
+
else:
|
|
16
|
+
base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config")
|
|
17
|
+
return os.path.join(base, "diffusr", "config.json")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def read_config() -> Optional[Dict[str, Any]]:
|
|
21
|
+
"""The saved config (it has a "key"), or None when there is none. Never raises."""
|
|
22
|
+
try:
|
|
23
|
+
with open(config_path(), "r", encoding="utf-8") as f:
|
|
24
|
+
data = json.load(f)
|
|
25
|
+
except (OSError, ValueError):
|
|
26
|
+
return None
|
|
27
|
+
if isinstance(data, dict) and isinstance(data.get("key"), str) and data["key"]:
|
|
28
|
+
return data
|
|
29
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: diffusr
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: The diffusr API client: make videos, images and music from code. Free price checks, idempotent starts, wait_for, signed callbacks.
|
|
5
5
|
Project-URL: Homepage, https://diffusr.ai
|
|
6
6
|
Project-URL: Documentation, https://diffusr.ai/docs
|
|
@@ -33,7 +33,7 @@ Mint keys on the Developers page in the app.
|
|
|
33
33
|
```python
|
|
34
34
|
from diffusr import Diffusr
|
|
35
35
|
|
|
36
|
-
diffusr = Diffusr() # reads DIFFUSR_KEY
|
|
36
|
+
diffusr = Diffusr() # reads DIFFUSR_KEY, else the key `npx diffusr login` saved
|
|
37
37
|
|
|
38
38
|
# 1. Ask the price. Free: nothing is made, nothing is spent.
|
|
39
39
|
quote = diffusr.make_video(
|
|
@@ -81,6 +81,28 @@ def hook():
|
|
|
81
81
|
constant-time compare, two signatures after a secret rotation, a 5-minute
|
|
82
82
|
timestamp window, `whsec_` prefix handled.
|
|
83
83
|
|
|
84
|
+
## Command line
|
|
85
|
+
|
|
86
|
+
Install the `diffusr` command with npm (it needs Node 18+) and log in once:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npx diffusr login # shows a code, opens your browser, saves a key
|
|
90
|
+
npx diffusr whoami
|
|
91
|
+
npx diffusr make_image "a mug on a sunlit table"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For scripts and agents add `--json` (only the JSON answer on stdout) and
|
|
95
|
+
`--yes` (allow spending; without it nothing is made). `npx diffusr help` lists
|
|
96
|
+
every command.
|
|
97
|
+
|
|
98
|
+
**Where the key is saved.** `~/.config/diffusr/config.json` (or
|
|
99
|
+
`$XDG_CONFIG_HOME/diffusr/config.json`; on Windows `%APPDATA%\diffusr\config.json`).
|
|
100
|
+
Only you can read it. `Diffusr()` uses it when no key is given.
|
|
101
|
+
|
|
102
|
+
**Which key wins.** The `api_key` argument, then the `DIFFUSR_KEY` variable,
|
|
103
|
+
then the saved file. The API address: the `base_url` argument, then
|
|
104
|
+
`DIFFUSR_BASE_URL`, then the saved file's `base_url`.
|
|
105
|
+
|
|
84
106
|
## Methods
|
|
85
107
|
|
|
86
108
|
| Method | REST |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
diffusr/__init__.py,sha256=Z_R1dexi8lV4krgWfXaecz5PRhcxkDtHo5fQgAaaQgg,600
|
|
2
|
+
diffusr/_client.py,sha256=iHriOw2jy9gahN63oHR-bk5wkVc7qMQcfms1UgYU5R8,13289
|
|
3
|
+
diffusr/_config.py,sha256=idMnlOT4UI1ic85e_ISsfpCGdUGq39XVxCrkB3q-NGU,1053
|
|
4
|
+
diffusr/_errors.py,sha256=YWrBDO9lzEqHgckTc66uLcpETfvbWmeyhmB2rFVJkzc,1184
|
|
5
|
+
diffusr/_verify.py,sha256=d5pNG-qf3CtXWuI5Ejm614kB96o0shMPE9xa34s_ZgM,1954
|
|
6
|
+
diffusr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
diffusr-0.2.0.dist-info/METADATA,sha256=ImEyCP3N1Lu_t0he-VrS6WCuiBqhNq1f9tOqp_K0KEw,6019
|
|
8
|
+
diffusr-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
9
|
+
diffusr-0.2.0.dist-info/licenses/LICENSE,sha256=1l1mtdgkeKxmibt4kNGlmLRfZhUQWNx-rJB0lQD_dWY,1064
|
|
10
|
+
diffusr-0.2.0.dist-info/RECORD,,
|
diffusr-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
diffusr/__init__.py,sha256=GJu0H3cqXBbB8VunnZqrzq0rqAGhOGSlEpP0i1Fasvo,516
|
|
2
|
-
diffusr/_client.py,sha256=MMyVDdmff1nIQSuiFKf_9c0sJDITUSY-fkNQjodgKyI,12005
|
|
3
|
-
diffusr/_errors.py,sha256=YWrBDO9lzEqHgckTc66uLcpETfvbWmeyhmB2rFVJkzc,1184
|
|
4
|
-
diffusr/_verify.py,sha256=d5pNG-qf3CtXWuI5Ejm614kB96o0shMPE9xa34s_ZgM,1954
|
|
5
|
-
diffusr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
diffusr-0.1.0.dist-info/METADATA,sha256=XzZOenx6fc7hiOEjMPj-vlsA6LzXjNtfus3xZDAC76A,5151
|
|
7
|
-
diffusr-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
8
|
-
diffusr-0.1.0.dist-info/licenses/LICENSE,sha256=1l1mtdgkeKxmibt4kNGlmLRfZhUQWNx-rJB0lQD_dWY,1064
|
|
9
|
-
diffusr-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|