onceonly-sdk 1.2.0__py3-none-any.whl → 2.0.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.
- onceonly/__init__.py +2 -2
- onceonly/_http.py +109 -0
- onceonly/_util.py +46 -0
- onceonly/ai.py +195 -0
- onceonly/ai_models.py +97 -0
- onceonly/client.py +134 -162
- onceonly/decorators.py +120 -26
- onceonly/exceptions.py +5 -1
- onceonly/integrations/__init__.py +1 -0
- onceonly/integrations/langchain.py +168 -0
- onceonly/models.py +11 -0
- onceonly/version.py +1 -0
- onceonly_sdk-2.0.0.dist-info/METADATA +140 -0
- onceonly_sdk-2.0.0.dist-info/RECORD +17 -0
- onceonly_sdk-1.2.0.dist-info/METADATA +0 -153
- onceonly_sdk-1.2.0.dist-info/RECORD +0 -10
- {onceonly_sdk-1.2.0.dist-info → onceonly_sdk-2.0.0.dist-info}/WHEEL +0 -0
- {onceonly_sdk-1.2.0.dist-info → onceonly_sdk-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {onceonly_sdk-1.2.0.dist-info → onceonly_sdk-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any, Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from ..client import OnceOnly
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _stable_hash_args(args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> str:
|
|
11
|
+
def default_encoder(obj: Any) -> Any:
|
|
12
|
+
# Pydantic v2
|
|
13
|
+
md = getattr(obj, "model_dump", None)
|
|
14
|
+
if callable(md):
|
|
15
|
+
try:
|
|
16
|
+
return md()
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
# Pydantic v1
|
|
21
|
+
dct = getattr(obj, "dict", None)
|
|
22
|
+
if callable(dct):
|
|
23
|
+
try:
|
|
24
|
+
return dct()
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
# Dataclasses
|
|
29
|
+
if hasattr(obj, "__dataclass_fields__"):
|
|
30
|
+
try:
|
|
31
|
+
import dataclasses
|
|
32
|
+
|
|
33
|
+
return dataclasses.asdict(obj)
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
38
|
+
return obj.hex()
|
|
39
|
+
|
|
40
|
+
return str(obj)
|
|
41
|
+
|
|
42
|
+
payload = {"args": args, "kwargs": {k: v for k, v in sorted(kwargs.items())}}
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
raw = json.dumps(
|
|
46
|
+
payload,
|
|
47
|
+
ensure_ascii=False,
|
|
48
|
+
default=default_encoder,
|
|
49
|
+
sort_keys=True,
|
|
50
|
+
separators=(",", ":"),
|
|
51
|
+
)
|
|
52
|
+
except Exception:
|
|
53
|
+
raw = str(payload)
|
|
54
|
+
|
|
55
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _hash_tool_input(tool_input: Any) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Stable hash for LangChain tool_input across versions.
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(tool_input, dict):
|
|
63
|
+
return _stable_hash_args((), tool_input)
|
|
64
|
+
return _stable_hash_args((tool_input,), {})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def make_idempotent_tool(
|
|
68
|
+
tool: Any,
|
|
69
|
+
*,
|
|
70
|
+
client: OnceOnly,
|
|
71
|
+
key_prefix: str = "tool",
|
|
72
|
+
ttl: int = 86400,
|
|
73
|
+
meta: Optional[Dict[str, Any]] = None,
|
|
74
|
+
) -> Any:
|
|
75
|
+
"""
|
|
76
|
+
Optional LangChain integration (no hard dependency).
|
|
77
|
+
Usage: pip install langchain-core
|
|
78
|
+
|
|
79
|
+
Wraps a BaseTool so repeated calls with the same tool_input become idempotent.
|
|
80
|
+
|
|
81
|
+
Implementation detail:
|
|
82
|
+
- We override invoke()/ainvoke() to avoid BaseTool._run signature differences across LC versions
|
|
83
|
+
and to support both single-input Tool and StructuredTool.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
from langchain_core.tools import BaseTool # type: ignore
|
|
87
|
+
except ImportError as e:
|
|
88
|
+
raise ImportError("LangChain is not installed. Install langchain-core to use this integration.") from e
|
|
89
|
+
|
|
90
|
+
if not isinstance(tool, BaseTool):
|
|
91
|
+
raise TypeError("tool must be an instance of langchain_core.tools.BaseTool")
|
|
92
|
+
|
|
93
|
+
base_meta: Dict[str, Any] = {"tool": getattr(tool, "name", tool.__class__.__name__)}
|
|
94
|
+
if meta:
|
|
95
|
+
base_meta.update(meta)
|
|
96
|
+
|
|
97
|
+
class IdempotentTool(BaseTool): # type: ignore[misc]
|
|
98
|
+
name: str
|
|
99
|
+
description: str
|
|
100
|
+
|
|
101
|
+
_tool: BaseTool
|
|
102
|
+
_client: OnceOnly
|
|
103
|
+
_key_prefix: str
|
|
104
|
+
_ttl: int
|
|
105
|
+
_meta: Dict[str, Any]
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
super().__init__(name=tool.name, description=tool.description)
|
|
109
|
+
object.__setattr__(self, "_tool", tool)
|
|
110
|
+
object.__setattr__(self, "_client", client)
|
|
111
|
+
object.__setattr__(self, "_key_prefix", key_prefix)
|
|
112
|
+
object.__setattr__(self, "_ttl", int(ttl))
|
|
113
|
+
object.__setattr__(self, "_meta", base_meta)
|
|
114
|
+
|
|
115
|
+
# Preserve schema + a few common attrs
|
|
116
|
+
if hasattr(tool, "args_schema"):
|
|
117
|
+
try:
|
|
118
|
+
object.__setattr__(self, "args_schema", getattr(tool, "args_schema"))
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
for attr in ("return_direct", "tags", "metadata", "callbacks", "verbose"):
|
|
123
|
+
if hasattr(tool, attr):
|
|
124
|
+
try:
|
|
125
|
+
setattr(self, attr, getattr(tool, attr))
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
def invoke(self, tool_input: Any, config: Any = None, **kwargs: Any) -> Any: # type: ignore[override]
|
|
130
|
+
h = _hash_tool_input(tool_input)
|
|
131
|
+
key = f"{self._key_prefix}:{self.name}:{h}"
|
|
132
|
+
|
|
133
|
+
res = self._client.check_lock(key=key, ttl=self._ttl, meta=self._meta)
|
|
134
|
+
if res.duplicate:
|
|
135
|
+
return f"Action '{self.name}' skipped (idempotency key duplicate)."
|
|
136
|
+
|
|
137
|
+
# Delegate: let LangChain handle parsing/validation/config
|
|
138
|
+
if config is None:
|
|
139
|
+
return self._tool.invoke(tool_input, **kwargs)
|
|
140
|
+
return self._tool.invoke(tool_input, config=config, **kwargs)
|
|
141
|
+
|
|
142
|
+
async def ainvoke(self, tool_input: Any, config: Any = None, **kwargs: Any) -> Any: # type: ignore[override]
|
|
143
|
+
h = _hash_tool_input(tool_input)
|
|
144
|
+
key = f"{self._key_prefix}:{self.name}:{h}"
|
|
145
|
+
|
|
146
|
+
res = await self._client.check_lock_async(key=key, ttl=self._ttl, meta=self._meta)
|
|
147
|
+
if res.duplicate:
|
|
148
|
+
return f"Action '{self.name}' skipped (idempotency key duplicate)."
|
|
149
|
+
|
|
150
|
+
ainvoke = getattr(self._tool, "ainvoke", None)
|
|
151
|
+
if callable(ainvoke):
|
|
152
|
+
if config is None:
|
|
153
|
+
return await ainvoke(tool_input, **kwargs)
|
|
154
|
+
return await ainvoke(tool_input, config=config, **kwargs)
|
|
155
|
+
|
|
156
|
+
# Fallback
|
|
157
|
+
if config is None:
|
|
158
|
+
return self._tool.invoke(tool_input, **kwargs)
|
|
159
|
+
return self._tool.invoke(tool_input, config=config, **kwargs)
|
|
160
|
+
|
|
161
|
+
# Keep BaseTool abstract contract satisfied; not used because we override invoke/ainvoke.
|
|
162
|
+
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
|
163
|
+
raise RuntimeError("IdempotentTool delegates via invoke(); _run() should not be called.")
|
|
164
|
+
|
|
165
|
+
async def _arun(self, *args: Any, **kwargs: Any) -> Any:
|
|
166
|
+
raise RuntimeError("IdempotentTool delegates via ainvoke(); _arun() should not be called.")
|
|
167
|
+
|
|
168
|
+
return IdempotentTool()
|
onceonly/models.py
CHANGED
|
@@ -14,3 +14,14 @@ class CheckLockResult:
|
|
|
14
14
|
request_id: Optional[str]
|
|
15
15
|
status_code: int
|
|
16
16
|
raw: Dict[str, Any]
|
|
17
|
+
|
|
18
|
+
def should_proceed(self) -> bool:
|
|
19
|
+
"""
|
|
20
|
+
Helper for agents/tools:
|
|
21
|
+
- True => proceed with the expensive/side-effect operation
|
|
22
|
+
- False => treat as duplicate (or blocked)
|
|
23
|
+
"""
|
|
24
|
+
return bool(self.locked) and not bool(self.duplicate)
|
|
25
|
+
|
|
26
|
+
def is_duplicate(self) -> bool:
|
|
27
|
+
return bool(self.duplicate)
|
onceonly/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.0"
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onceonly-sdk
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Python SDK for OnceOnly idempotency API
|
|
5
|
+
Author-email: OnceOnly <support@onceonly.tech>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://onceonly.tech/
|
|
8
|
+
Project-URL: Documentation, https://onceonly.tech/docs/
|
|
9
|
+
Project-URL: Repository, https://github.com/mykolademyanov/onceonly-python
|
|
10
|
+
Keywords: idempotency,automation,zapier,make,ai-agents
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: httpx>=0.25
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
21
|
+
Requires-Dist: anyio>=4.0; extra == "test"
|
|
22
|
+
Provides-Extra: langchain
|
|
23
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# OnceOnly Python SDK
|
|
27
|
+
|
|
28
|
+
**The Idempotency Layer for AI Agents, Webhooks, and Distributed Systems.**
|
|
29
|
+
|
|
30
|
+
OnceOnly is a high-performance Python SDK that ensures **exactly-once execution**.
|
|
31
|
+
It prevents duplicate actions (payments, emails, tool calls) in unstable environments like
|
|
32
|
+
AI agents, webhooks, retries, or background workers.
|
|
33
|
+
|
|
34
|
+
Website: https://onceonly.tech/ai/
|
|
35
|
+
Documentation: https://onceonly.tech/docs/
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- Sync + Async client (httpx-based)
|
|
42
|
+
- Fail-open mode for production safety
|
|
43
|
+
- Stable idempotency keys (supports Pydantic & dataclasses)
|
|
44
|
+
- Decorator for zero-boilerplate usage
|
|
45
|
+
- Optional AI / LangChain integrations
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install onceonly-sdk
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### With LangChain support included:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install "onceonly-sdk[langchain]"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from onceonly import OnceOnly
|
|
67
|
+
|
|
68
|
+
client = OnceOnly(
|
|
69
|
+
api_key="once_live_...",
|
|
70
|
+
fail_open=True # default: continues if API is down
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
res = client.check_lock(key="order:123", ttl=300)
|
|
74
|
+
|
|
75
|
+
if res.duplicate:
|
|
76
|
+
print("Duplicate blocked")
|
|
77
|
+
else:
|
|
78
|
+
print("First execution")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## AI Agents / LangChain Integration 🤖
|
|
84
|
+
|
|
85
|
+
OnceOnly integrates cleanly with AI-agent frameworks like LangChain.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from onceonly.integrations.langchain import make_idempotent_tool
|
|
89
|
+
|
|
90
|
+
tool = make_idempotent_tool(
|
|
91
|
+
original_tool,
|
|
92
|
+
client=client,
|
|
93
|
+
key_prefix="agent:tool"
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Repeated tool calls with the same inputs will execute **exactly once**, even across retries or agent restarts.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Decorator
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from onceonly.decorators import idempotent
|
|
105
|
+
|
|
106
|
+
@idempotent(client, ttl=3600)
|
|
107
|
+
def process_order(order_id):
|
|
108
|
+
...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Idempotency keys are generated automatically and are stable across restarts.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Fail-Open Mode
|
|
116
|
+
|
|
117
|
+
Fail-open is enabled by default.
|
|
118
|
+
|
|
119
|
+
Network errors, timeouts, or server errors (5xx) will **not break your application**.
|
|
120
|
+
The SDK will allow execution to continue safely.
|
|
121
|
+
|
|
122
|
+
Fail-open never applies to:
|
|
123
|
+
- Auth errors (401 / 403)
|
|
124
|
+
- Plan limits (402)
|
|
125
|
+
- Validation errors (422)
|
|
126
|
+
- Rate limits (429)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Support
|
|
131
|
+
|
|
132
|
+
Need help?
|
|
133
|
+
Email: support@onceonly.tech
|
|
134
|
+
Or open an issue on GitHub.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
onceonly/__init__.py,sha256=KMS6F4DejM5nI5-gw3UC8SvETnK90oUE9V5pskh--Uw,481
|
|
2
|
+
onceonly/_http.py,sha256=bFAgrLv0T7cGFq3LqaQCwEiqx-VfKEiT8jUommmhRws,3240
|
|
3
|
+
onceonly/_util.py,sha256=YVdEWn1bvipAzR3g3oXpHmgLiaODwGRB1IGA3gHZ2PM,1273
|
|
4
|
+
onceonly/ai.py,sha256=NjMHtZgc-a-l1Wr3mTWwL9HnIOLZbVr9gkuMXMHbuqA,7043
|
|
5
|
+
onceonly/ai_models.py,sha256=7bHYnAavdb3c-4nlh9HgRY18949TgmU9XfXfv3PXQEE,2910
|
|
6
|
+
onceonly/client.py,sha256=6DtLdWc-7_bAXsaaewUQUTHVnCkRZGsc-PByMVPRhYY,12838
|
|
7
|
+
onceonly/decorators.py,sha256=nP7Wu-RAQQNaTwyOnibzClEgcBJvYheMrG3_KztdlG8,5171
|
|
8
|
+
onceonly/exceptions.py,sha256=Issh08A4IHSDaysJhVZNRCU9W_9BfiGt65UHaMhDCs4,1156
|
|
9
|
+
onceonly/models.py,sha256=hVEBPgIVZP3ELjWYIFSFCKPzI38t5DA0gio9FvrmHJg,678
|
|
10
|
+
onceonly/version.py,sha256=_7OlQdbVkK4jad0CLdpI0grT-zEAb-qgFmH5mFzDXiA,22
|
|
11
|
+
onceonly/integrations/__init__.py,sha256=0tk-2HTTsmc42NhWuR_G_Afmz5-5WG8NvmlO7iIPkIY,34
|
|
12
|
+
onceonly/integrations/langchain.py,sha256=cdpHIluddX48uYeDeE1cxmn-arruVdE3k6gvZxYC9z4,5821
|
|
13
|
+
onceonly_sdk-2.0.0.dist-info/licenses/LICENSE,sha256=YQQ8IT_P7hcGmmLFFuOy3eKDZ90e1cqef_okg85oAiQ,129
|
|
14
|
+
onceonly_sdk-2.0.0.dist-info/METADATA,sha256=3cso7k9xZoja4JR8VwZuT4QtnclBcR9cdB4zKYnOM1w,3080
|
|
15
|
+
onceonly_sdk-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
+
onceonly_sdk-2.0.0.dist-info/top_level.txt,sha256=lvz-sHerZcTwlZW-uYoda_wgx62kY07GdtzIdw89hnU,9
|
|
17
|
+
onceonly_sdk-2.0.0.dist-info/RECORD,,
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: onceonly-sdk
|
|
3
|
-
Version: 1.2.0
|
|
4
|
-
Summary: Python SDK for OnceOnly idempotency API
|
|
5
|
-
Author-email: OnceOnly <support@onceonly.tech>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://onceonly.tech/
|
|
8
|
-
Project-URL: Documentation, https://onceonly.tech/docs/
|
|
9
|
-
Project-URL: Repository, https://github.com/mykolademyanov/onceonly-python
|
|
10
|
-
Keywords: idempotency,automation,zapier,make,ai-agents
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Requires-Python: >=3.9
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
Requires-Dist: httpx>=0.25
|
|
18
|
-
Provides-Extra: test
|
|
19
|
-
Requires-Dist: pytest>=7.0; extra == "test"
|
|
20
|
-
Dynamic: license-file
|
|
21
|
-
|
|
22
|
-
# OnceOnly Python SDK
|
|
23
|
-
|
|
24
|
-
**The Idempotency Layer for AI Agents, Webhooks, and Distributed Systems.**
|
|
25
|
-
|
|
26
|
-
OnceOnly is a high-performance Python SDK designed to ensure **exactly-once execution**.
|
|
27
|
-
It prevents duplicate actions (payments, emails, tool calls) in unstable environments like
|
|
28
|
-
AI agents, webhooks, or background workers.
|
|
29
|
-
|
|
30
|
-
Website - https://onceonly.tech/ai/
|
|
31
|
-
|
|
32
|
-
[](https://pypi.org/project/onceonly-sdk/)
|
|
33
|
-
[](https://opensource.org/licenses/MIT)
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
- **Sync + Async Client** — built on httpx for modern Python stacks
|
|
40
|
-
- **Connection Pooling** — high performance under heavy load
|
|
41
|
-
- **Fail-Open Mode** — business logic keeps running even if API is unreachable
|
|
42
|
-
- **Smart Decorator** — automatic idempotency based on function arguments
|
|
43
|
-
- **Typed Results & Exceptions**
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Installation
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
pip install onceonly-sdk
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Quick Start
|
|
56
|
-
|
|
57
|
-
```python
|
|
58
|
-
from onceonly import OnceOnly
|
|
59
|
-
|
|
60
|
-
client = OnceOnly(api_key="once_live_...")
|
|
61
|
-
|
|
62
|
-
result = client.check_lock(
|
|
63
|
-
key="order:123",
|
|
64
|
-
ttl=300, # 300 seconds = 5 minutes (clamped by your plan)
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
if result.duplicate:
|
|
68
|
-
print("Duplicate blocked")
|
|
69
|
-
else:
|
|
70
|
-
print("First execution")
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Async Usage
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
async def handler():
|
|
79
|
-
result = await client.check_lock_async("order:123")
|
|
80
|
-
if result.locked:
|
|
81
|
-
print("Locked")
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## TTL Behavior
|
|
87
|
-
|
|
88
|
-
- TTL is specified in seconds
|
|
89
|
-
- If ttl is not provided, the server applies the plan default TTL
|
|
90
|
-
- If ttl is provided, it is automatically clamped to your plan limits
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Metadata
|
|
95
|
-
|
|
96
|
-
You can optionally attach metadata to each check-lock call.
|
|
97
|
-
Metadata is useful for debugging, tracing, and server-side analytics.
|
|
98
|
-
|
|
99
|
-
Rules:
|
|
100
|
-
- JSON-serializable only
|
|
101
|
-
- Size-limited
|
|
102
|
-
- Safely logged on the server
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Decorator
|
|
107
|
-
|
|
108
|
-
The SDK provides an optional decorator that automatically generates
|
|
109
|
-
an idempotency key based on the **function name and arguments**.
|
|
110
|
-
|
|
111
|
-
This allows you to add exactly-once guarantees to existing code
|
|
112
|
-
with zero manual key management.
|
|
113
|
-
|
|
114
|
-
```python
|
|
115
|
-
from onceonly.decorators import idempotent
|
|
116
|
-
|
|
117
|
-
@idempotent(client, ttl=3600)
|
|
118
|
-
def process_order(order_id):
|
|
119
|
-
...
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Fail-Open Mode
|
|
125
|
-
|
|
126
|
-
Enabled by default.
|
|
127
|
-
|
|
128
|
-
If a network error, timeout, or server error (5xx) occurs, the SDK returns a locked result
|
|
129
|
-
instead of breaking your application.
|
|
130
|
-
|
|
131
|
-
Fail-open never triggers for:
|
|
132
|
-
- Authentication errors (401 / 403)
|
|
133
|
-
- Plan limits (402)
|
|
134
|
-
- Validation errors (422)
|
|
135
|
-
- Rate limits (429)
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## Exceptions
|
|
140
|
-
|
|
141
|
-
| Exception | HTTP Status | Description |
|
|
142
|
-
|--------------------|------------|------------------------------------------|
|
|
143
|
-
| UnauthorizedError | 401 / 403 | Invalid or disabled API key |
|
|
144
|
-
| OverLimitError | 402 | Plan limit reached |
|
|
145
|
-
| RateLimitError | 429 | Too many requests |
|
|
146
|
-
| ValidationError | 422 | Invalid input |
|
|
147
|
-
| ApiError | 5xx / other| Server or unexpected API error |
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## License
|
|
152
|
-
|
|
153
|
-
MIT
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
onceonly/__init__.py,sha256=_SwJ4Y2345WJKfpW2IsOVfgNaUzjZqTSSOnPQBroW-M,452
|
|
2
|
-
onceonly/client.py,sha256=1RMTkGDpalHfx-xEH8DZsn9ZJ3VeGomG6PKAQQjPdZQ,13361
|
|
3
|
-
onceonly/decorators.py,sha256=9eBhRUQDBGTUXVyRYlpYy77y-EHF-udX3ERkrfIt9Kg,2651
|
|
4
|
-
onceonly/exceptions.py,sha256=RP556LUtO54TPFTZybF4dVA9n3TByFn35esz7EbcrpY,994
|
|
5
|
-
onceonly/models.py,sha256=xhPfP1kpyHYfKJNiac2REeXTpPLzrEWJeqn-p6B6taQ,329
|
|
6
|
-
onceonly_sdk-1.2.0.dist-info/licenses/LICENSE,sha256=YQQ8IT_P7hcGmmLFFuOy3eKDZ90e1cqef_okg85oAiQ,129
|
|
7
|
-
onceonly_sdk-1.2.0.dist-info/METADATA,sha256=dvml-Mhrd_n0xFEhk7-rvShxiYfaLcl_Pq2vyGD0XRo,3891
|
|
8
|
-
onceonly_sdk-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
onceonly_sdk-1.2.0.dist-info/top_level.txt,sha256=lvz-sHerZcTwlZW-uYoda_wgx62kY07GdtzIdw89hnU,9
|
|
10
|
-
onceonly_sdk-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|