synchronity-sdk 0.1.1__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.
- agentmesh/__init__.py +62 -0
- agentmesh/client.py +44 -0
- agentmesh/config.py +18 -0
- agentmesh/errors.py +97 -0
- agentmesh/http.py +139 -0
- agentmesh/modules/__init__.py +14 -0
- agentmesh/modules/cart.py +51 -0
- agentmesh/modules/checkout.py +49 -0
- agentmesh/modules/discovery.py +42 -0
- agentmesh/modules/orders.py +36 -0
- agentmesh/modules/products.py +73 -0
- agentmesh/tools/__init__.py +6 -0
- agentmesh/tools/_defs.py +195 -0
- agentmesh/tools/anthropic.py +13 -0
- agentmesh/tools/executor.py +107 -0
- agentmesh/tools/langchain.py +13 -0
- agentmesh/tools/openai.py +16 -0
- agentmesh/types/__init__.py +57 -0
- agentmesh/types/amps.py +234 -0
- synchronity_sdk-0.1.1.dist-info/METADATA +181 -0
- synchronity_sdk-0.1.1.dist-info/RECORD +23 -0
- synchronity_sdk-0.1.1.dist-info/WHEEL +5 -0
- synchronity_sdk-0.1.1.dist-info/top_level.txt +1 -0
agentmesh/__init__.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from .client import AgentMeshClient
|
|
2
|
+
from .config import AgentMeshConfig, SDK_VERSION
|
|
3
|
+
from .errors import (
|
|
4
|
+
AgentMeshError,
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
AuthorizationError,
|
|
7
|
+
ConnectorError,
|
|
8
|
+
NetworkError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
TimeoutError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
)
|
|
14
|
+
from .http import HttpClient
|
|
15
|
+
from .modules import (
|
|
16
|
+
CartModule,
|
|
17
|
+
CheckoutModule,
|
|
18
|
+
DiscoveryModule,
|
|
19
|
+
OrdersModule,
|
|
20
|
+
ProductsModule,
|
|
21
|
+
ShippingAddressParams,
|
|
22
|
+
)
|
|
23
|
+
from .types import (
|
|
24
|
+
AMPSCapabilityManifest,
|
|
25
|
+
AMPSCart,
|
|
26
|
+
AMPSOrder,
|
|
27
|
+
AMPSOrderStatus,
|
|
28
|
+
AMPSProduct,
|
|
29
|
+
AvailabilityStatus,
|
|
30
|
+
MonetaryAmount,
|
|
31
|
+
SiteListItem,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AgentMeshClient",
|
|
36
|
+
"AgentMeshConfig",
|
|
37
|
+
"SDK_VERSION",
|
|
38
|
+
"AgentMeshError",
|
|
39
|
+
"AuthenticationError",
|
|
40
|
+
"AuthorizationError",
|
|
41
|
+
"ConnectorError",
|
|
42
|
+
"NetworkError",
|
|
43
|
+
"NotFoundError",
|
|
44
|
+
"RateLimitError",
|
|
45
|
+
"TimeoutError",
|
|
46
|
+
"ValidationError",
|
|
47
|
+
"HttpClient",
|
|
48
|
+
"CartModule",
|
|
49
|
+
"CheckoutModule",
|
|
50
|
+
"DiscoveryModule",
|
|
51
|
+
"OrdersModule",
|
|
52
|
+
"ProductsModule",
|
|
53
|
+
"ShippingAddressParams",
|
|
54
|
+
"AMPSCapabilityManifest",
|
|
55
|
+
"AMPSCart",
|
|
56
|
+
"AMPSOrder",
|
|
57
|
+
"AMPSOrderStatus",
|
|
58
|
+
"AMPSProduct",
|
|
59
|
+
"AvailabilityStatus",
|
|
60
|
+
"MonetaryAmount",
|
|
61
|
+
"SiteListItem",
|
|
62
|
+
]
|
agentmesh/client.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .config import AgentMeshConfig
|
|
6
|
+
from .http import HttpClient
|
|
7
|
+
from .modules.cart import CartModule
|
|
8
|
+
from .modules.checkout import CheckoutModule
|
|
9
|
+
from .modules.discovery import DiscoveryModule
|
|
10
|
+
from .modules.orders import OrdersModule
|
|
11
|
+
from .modules.products import ProductsModule
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentMeshClient:
|
|
15
|
+
def __init__(self, config: AgentMeshConfig) -> None:
|
|
16
|
+
self._http = HttpClient(config)
|
|
17
|
+
self.discovery = DiscoveryModule(self._http)
|
|
18
|
+
self.products = ProductsModule(self._http)
|
|
19
|
+
self.cart = CartModule(self._http)
|
|
20
|
+
self.checkout = CheckoutModule(self._http)
|
|
21
|
+
self.orders = OrdersModule(self._http)
|
|
22
|
+
|
|
23
|
+
async def __aenter__(self) -> "AgentMeshClient":
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
27
|
+
await self._http.close()
|
|
28
|
+
|
|
29
|
+
async def close(self) -> None:
|
|
30
|
+
await self._http.close()
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def with_http_client(
|
|
34
|
+
cls, config: AgentMeshConfig, http_client: HttpClient
|
|
35
|
+
) -> "AgentMeshClient":
|
|
36
|
+
"""Inject a pre-built HttpClient — useful for testing."""
|
|
37
|
+
instance = cls.__new__(cls)
|
|
38
|
+
instance._http = http_client
|
|
39
|
+
instance.discovery = DiscoveryModule(http_client)
|
|
40
|
+
instance.products = ProductsModule(http_client)
|
|
41
|
+
instance.cart = CartModule(http_client)
|
|
42
|
+
instance.checkout = CheckoutModule(http_client)
|
|
43
|
+
instance.orders = OrdersModule(http_client)
|
|
44
|
+
return instance
|
agentmesh/config.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
SDK_VERSION = "0.1.0"
|
|
6
|
+
DEFAULT_BASE_URL = "https://gateway.agentmesh.ai"
|
|
7
|
+
DEFAULT_TIMEOUT = 30.0
|
|
8
|
+
DEFAULT_RETRIES = 3
|
|
9
|
+
DEFAULT_RETRY_DELAY = 1.0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class AgentMeshConfig:
|
|
14
|
+
agent_token: str
|
|
15
|
+
base_url: str = DEFAULT_BASE_URL
|
|
16
|
+
timeout: float = DEFAULT_TIMEOUT
|
|
17
|
+
retries: int = DEFAULT_RETRIES
|
|
18
|
+
retry_delay: float = DEFAULT_RETRY_DELAY
|
agentmesh/errors.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AgentMeshError(Exception):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
message: str,
|
|
9
|
+
code: str,
|
|
10
|
+
status_code: int,
|
|
11
|
+
request_id: Optional[str] = None,
|
|
12
|
+
) -> None:
|
|
13
|
+
super().__init__(message)
|
|
14
|
+
self.code = code
|
|
15
|
+
self.status_code = status_code
|
|
16
|
+
self.request_id = request_id
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AuthenticationError(AgentMeshError):
|
|
20
|
+
"""Raised on HTTP 401 responses."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AuthorizationError(AgentMeshError):
|
|
24
|
+
"""Raised on HTTP 403 responses."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NotFoundError(AgentMeshError):
|
|
28
|
+
"""Raised on HTTP 404 / 410 responses."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ValidationError(AgentMeshError):
|
|
32
|
+
"""Raised on HTTP 400 responses."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RateLimitError(AgentMeshError):
|
|
36
|
+
"""Raised on HTTP 429 responses."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
message: str,
|
|
41
|
+
code: str,
|
|
42
|
+
status_code: int,
|
|
43
|
+
request_id: Optional[str] = None,
|
|
44
|
+
retry_after: int = 60,
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(message, code, status_code, request_id)
|
|
47
|
+
self.retry_after = retry_after
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConnectorError(AgentMeshError):
|
|
51
|
+
"""Raised on HTTP 422 responses (upstream connector failure)."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class NetworkError(AgentMeshError):
|
|
55
|
+
"""Raised on network-level failures (connection errors, etc.)."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, message: str, request_id: Optional[str] = None) -> None:
|
|
58
|
+
super().__init__(message, "NETWORK_ERROR", 0, request_id)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TimeoutError(AgentMeshError):
|
|
62
|
+
"""Raised when a request times out."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, message: str, request_id: Optional[str] = None) -> None:
|
|
65
|
+
super().__init__(message, "TIMEOUT", 0, request_id)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_error_response(
|
|
69
|
+
status: int,
|
|
70
|
+
body: dict,
|
|
71
|
+
request_id: Optional[str] = None,
|
|
72
|
+
retry_after: Optional[int] = None,
|
|
73
|
+
) -> AgentMeshError:
|
|
74
|
+
error = body.get("error", {})
|
|
75
|
+
code = error.get("code", "UNKNOWN_ERROR")
|
|
76
|
+
message = error.get("message", f"HTTP {status}")
|
|
77
|
+
|
|
78
|
+
if status == 400:
|
|
79
|
+
return ValidationError(message, code, status, request_id)
|
|
80
|
+
elif status == 401:
|
|
81
|
+
return AuthenticationError(message, code, status, request_id)
|
|
82
|
+
elif status == 403:
|
|
83
|
+
return AuthorizationError(message, code, status, request_id)
|
|
84
|
+
elif status in (404, 410):
|
|
85
|
+
return NotFoundError(message, code, status, request_id)
|
|
86
|
+
elif status == 422:
|
|
87
|
+
return ConnectorError(message, code, status, request_id)
|
|
88
|
+
elif status == 429:
|
|
89
|
+
return RateLimitError(
|
|
90
|
+
message,
|
|
91
|
+
code,
|
|
92
|
+
status,
|
|
93
|
+
request_id,
|
|
94
|
+
retry_after=retry_after if retry_after is not None else 60,
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
return AgentMeshError(message, code, status, request_id)
|
agentmesh/http.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Dict, Optional, Union
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .config import AgentMeshConfig, SDK_VERSION
|
|
9
|
+
from .errors import (
|
|
10
|
+
AgentMeshError,
|
|
11
|
+
NetworkError,
|
|
12
|
+
TimeoutError,
|
|
13
|
+
parse_error_response,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HttpClient:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
config: AgentMeshConfig,
|
|
21
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
self._config = config
|
|
24
|
+
self._base_url = config.base_url.rstrip("/")
|
|
25
|
+
self._headers = {
|
|
26
|
+
"Authorization": f"Bearer {config.agent_token}",
|
|
27
|
+
"User-Agent": f"agentmesh-python/{SDK_VERSION}",
|
|
28
|
+
"Accept": "application/json",
|
|
29
|
+
}
|
|
30
|
+
self._owned = http_client is None
|
|
31
|
+
self._client = http_client or httpx.AsyncClient(
|
|
32
|
+
timeout=config.timeout,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def __aenter__(self) -> "HttpClient":
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
39
|
+
await self.close()
|
|
40
|
+
|
|
41
|
+
async def close(self) -> None:
|
|
42
|
+
if self._owned:
|
|
43
|
+
await self._client.aclose()
|
|
44
|
+
|
|
45
|
+
async def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> dict:
|
|
46
|
+
return await self._request("GET", path, params=params)
|
|
47
|
+
|
|
48
|
+
async def post(self, path: str, body: Optional[dict] = None) -> dict:
|
|
49
|
+
return await self._request("POST", path, body=body)
|
|
50
|
+
|
|
51
|
+
async def delete(self, path: str) -> dict:
|
|
52
|
+
return await self._request("DELETE", path)
|
|
53
|
+
|
|
54
|
+
def _build_url(self, path: str) -> str:
|
|
55
|
+
return f"{self._base_url}{path}"
|
|
56
|
+
|
|
57
|
+
def _clean_params(
|
|
58
|
+
self, params: Optional[Dict[str, Any]]
|
|
59
|
+
) -> Optional[Dict[str, str]]:
|
|
60
|
+
if not params:
|
|
61
|
+
return None
|
|
62
|
+
result: Dict[str, str] = {}
|
|
63
|
+
for k, v in params.items():
|
|
64
|
+
if v is None:
|
|
65
|
+
continue
|
|
66
|
+
if isinstance(v, list):
|
|
67
|
+
result[k] = ",".join(str(i) for i in v)
|
|
68
|
+
else:
|
|
69
|
+
result[k] = str(v)
|
|
70
|
+
return result or None
|
|
71
|
+
|
|
72
|
+
async def _request(
|
|
73
|
+
self,
|
|
74
|
+
method: str,
|
|
75
|
+
path: str,
|
|
76
|
+
params: Optional[Dict[str, Any]] = None,
|
|
77
|
+
body: Optional[dict] = None,
|
|
78
|
+
attempt: int = 0,
|
|
79
|
+
) -> dict:
|
|
80
|
+
url = self._build_url(path)
|
|
81
|
+
cleaned_params = self._clean_params(params)
|
|
82
|
+
|
|
83
|
+
headers = dict(self._headers)
|
|
84
|
+
if body is not None:
|
|
85
|
+
headers["Content-Type"] = "application/json"
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
response = await self._client.request(
|
|
89
|
+
method,
|
|
90
|
+
url,
|
|
91
|
+
params=cleaned_params,
|
|
92
|
+
json=body,
|
|
93
|
+
headers=headers,
|
|
94
|
+
)
|
|
95
|
+
except httpx.TimeoutException as exc:
|
|
96
|
+
raise TimeoutError(f"Request timed out: {method} {path}") from exc
|
|
97
|
+
except httpx.RequestError as exc:
|
|
98
|
+
raise NetworkError(f"Network error: {method} {path}: {exc}") from exc
|
|
99
|
+
|
|
100
|
+
request_id = response.headers.get("X-AgentMesh-Request-Id")
|
|
101
|
+
|
|
102
|
+
if response.is_success:
|
|
103
|
+
if response.status_code == 204:
|
|
104
|
+
return {}
|
|
105
|
+
return response.json()
|
|
106
|
+
|
|
107
|
+
# Parse retry-after header for 429
|
|
108
|
+
retry_after: Optional[int] = None
|
|
109
|
+
raw_retry = response.headers.get("Retry-After")
|
|
110
|
+
if raw_retry is not None:
|
|
111
|
+
try:
|
|
112
|
+
retry_after = int(raw_retry)
|
|
113
|
+
except ValueError:
|
|
114
|
+
retry_after = 60
|
|
115
|
+
|
|
116
|
+
# Retry on 429 and 5xx
|
|
117
|
+
should_retry = attempt < self._config.retries and (
|
|
118
|
+
response.status_code == 429 or response.status_code >= 500
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if should_retry:
|
|
122
|
+
if response.status_code == 429 and retry_after is not None:
|
|
123
|
+
delay = float(retry_after)
|
|
124
|
+
else:
|
|
125
|
+
delay = self._config.retry_delay * (2**attempt)
|
|
126
|
+
await asyncio.sleep(delay)
|
|
127
|
+
return await self._request(method, path, params=params, body=body, attempt=attempt + 1)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
error_body = response.json()
|
|
131
|
+
except Exception:
|
|
132
|
+
error_body = {
|
|
133
|
+
"error": {
|
|
134
|
+
"code": "UNKNOWN_ERROR",
|
|
135
|
+
"message": f"HTTP {response.status_code}: {response.text}",
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
raise parse_error_response(response.status_code, error_body, request_id, retry_after)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .cart import CartModule
|
|
2
|
+
from .checkout import CheckoutModule, ShippingAddressParams
|
|
3
|
+
from .discovery import DiscoveryModule
|
|
4
|
+
from .orders import OrdersModule
|
|
5
|
+
from .products import ProductsModule
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CartModule",
|
|
9
|
+
"CheckoutModule",
|
|
10
|
+
"ShippingAddressParams",
|
|
11
|
+
"DiscoveryModule",
|
|
12
|
+
"OrdersModule",
|
|
13
|
+
"ProductsModule",
|
|
14
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
from ..http import HttpClient
|
|
5
|
+
from ..types.amps import AMPSCart
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CartModule:
|
|
9
|
+
def __init__(self, http: HttpClient) -> None:
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
async def create(self, site_id: str, currency: Optional[str] = None) -> AMPSCart:
|
|
13
|
+
body: dict = {}
|
|
14
|
+
if currency is not None:
|
|
15
|
+
body["currency"] = currency
|
|
16
|
+
raw = await self._http.post(f"/v1/sites/{site_id}/cart", body)
|
|
17
|
+
return AMPSCart.model_validate(raw)
|
|
18
|
+
|
|
19
|
+
async def get(self, site_id: str, cart_id: str) -> AMPSCart:
|
|
20
|
+
raw = await self._http.get(f"/v1/sites/{site_id}/cart/{cart_id}")
|
|
21
|
+
return AMPSCart.model_validate(raw)
|
|
22
|
+
|
|
23
|
+
async def add_item(
|
|
24
|
+
self,
|
|
25
|
+
site_id: str,
|
|
26
|
+
cart_id: str,
|
|
27
|
+
product_id: str,
|
|
28
|
+
quantity: int,
|
|
29
|
+
variant_id: Optional[str] = None,
|
|
30
|
+
) -> AMPSCart:
|
|
31
|
+
body: dict = {"product_id": product_id, "quantity": quantity}
|
|
32
|
+
if variant_id is not None:
|
|
33
|
+
body["variant_id"] = variant_id
|
|
34
|
+
raw = await self._http.post(f"/v1/sites/{site_id}/cart/{cart_id}/items", body)
|
|
35
|
+
return AMPSCart.model_validate(raw)
|
|
36
|
+
|
|
37
|
+
async def remove_item(
|
|
38
|
+
self, site_id: str, cart_id: str, item_id: str
|
|
39
|
+
) -> AMPSCart:
|
|
40
|
+
raw = await self._http.delete(
|
|
41
|
+
f"/v1/sites/{site_id}/cart/{cart_id}/items/{item_id}"
|
|
42
|
+
)
|
|
43
|
+
return AMPSCart.model_validate(raw)
|
|
44
|
+
|
|
45
|
+
async def apply_coupon(
|
|
46
|
+
self, site_id: str, cart_id: str, code: str
|
|
47
|
+
) -> AMPSCart:
|
|
48
|
+
raw = await self._http.post(
|
|
49
|
+
f"/v1/sites/{site_id}/cart/{cart_id}/coupon", {"code": code}
|
|
50
|
+
)
|
|
51
|
+
return AMPSCart.model_validate(raw)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from ..http import HttpClient
|
|
7
|
+
from ..types.amps import AMPSOrder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ShippingAddressParams:
|
|
12
|
+
name: str
|
|
13
|
+
line1: str
|
|
14
|
+
city: str
|
|
15
|
+
state: str
|
|
16
|
+
postal_code: str
|
|
17
|
+
country: str
|
|
18
|
+
line2: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CheckoutModule:
|
|
22
|
+
def __init__(self, http: HttpClient) -> None:
|
|
23
|
+
self._http = http
|
|
24
|
+
|
|
25
|
+
async def execute(
|
|
26
|
+
self,
|
|
27
|
+
site_id: str,
|
|
28
|
+
cart_id: str,
|
|
29
|
+
buyer_delegation_token: str,
|
|
30
|
+
shipping_address: ShippingAddressParams,
|
|
31
|
+
) -> AMPSOrder:
|
|
32
|
+
address: dict = {
|
|
33
|
+
"name": shipping_address.name,
|
|
34
|
+
"line1": shipping_address.line1,
|
|
35
|
+
"city": shipping_address.city,
|
|
36
|
+
"state": shipping_address.state,
|
|
37
|
+
"postal_code": shipping_address.postal_code,
|
|
38
|
+
"country_code": shipping_address.country,
|
|
39
|
+
}
|
|
40
|
+
if shipping_address.line2 is not None:
|
|
41
|
+
address["line2"] = shipping_address.line2
|
|
42
|
+
|
|
43
|
+
body = {
|
|
44
|
+
"cart_id": cart_id,
|
|
45
|
+
"buyer_delegation_token": buyer_delegation_token,
|
|
46
|
+
"shipping_address": address,
|
|
47
|
+
}
|
|
48
|
+
raw = await self._http.post(f"/v1/sites/{site_id}/checkout", body)
|
|
49
|
+
return AMPSOrder.model_validate(raw)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Union, List
|
|
3
|
+
|
|
4
|
+
from ..http import HttpClient
|
|
5
|
+
from ..types.amps import AMPSCapabilityManifest, PaginatedSiteResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DiscoveryModule:
|
|
9
|
+
def __init__(self, http: HttpClient) -> None:
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
async def list_sites(
|
|
13
|
+
self,
|
|
14
|
+
category: Optional[str] = None,
|
|
15
|
+
ships_to: Optional[str] = None,
|
|
16
|
+
capabilities: Optional[List[str]] = None,
|
|
17
|
+
page: Optional[int] = None,
|
|
18
|
+
limit: Optional[int] = None,
|
|
19
|
+
) -> dict:
|
|
20
|
+
params: dict = {}
|
|
21
|
+
if category is not None:
|
|
22
|
+
params["category"] = category
|
|
23
|
+
if ships_to is not None:
|
|
24
|
+
params["ships_to"] = ships_to
|
|
25
|
+
if capabilities is not None:
|
|
26
|
+
params["capabilities"] = capabilities
|
|
27
|
+
if page is not None:
|
|
28
|
+
params["page"] = page
|
|
29
|
+
if limit is not None:
|
|
30
|
+
params["limit"] = limit
|
|
31
|
+
|
|
32
|
+
raw = await self._http.get("/v1/sites", params or None)
|
|
33
|
+
response = PaginatedSiteResponse.model_validate(raw)
|
|
34
|
+
return {
|
|
35
|
+
"sites": [s.model_dump() for s in response.data],
|
|
36
|
+
"total": response.pagination.total,
|
|
37
|
+
"page": response.pagination.page,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async def get_manifest(self, site_id: str) -> AMPSCapabilityManifest:
|
|
41
|
+
raw = await self._http.get(f"/v1/sites/{site_id}/manifest")
|
|
42
|
+
return AMPSCapabilityManifest.model_validate(raw)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
from ..http import HttpClient
|
|
5
|
+
from ..types.amps import AMPSOrder, AMPSOrderStatus, PaginatedOrderResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OrdersModule:
|
|
9
|
+
def __init__(self, http: HttpClient) -> None:
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
async def get(self, site_id: str, order_id: str) -> AMPSOrder:
|
|
13
|
+
raw = await self._http.get(f"/v1/sites/{site_id}/orders/{order_id}")
|
|
14
|
+
return AMPSOrder.model_validate(raw)
|
|
15
|
+
|
|
16
|
+
async def list(
|
|
17
|
+
self,
|
|
18
|
+
site_id: str,
|
|
19
|
+
page: Optional[int] = None,
|
|
20
|
+
limit: Optional[int] = None,
|
|
21
|
+
status: AMPSOrderStatus | Optional[str] = None,
|
|
22
|
+
) -> dict:
|
|
23
|
+
params: dict = {}
|
|
24
|
+
if page is not None:
|
|
25
|
+
params["page"] = page
|
|
26
|
+
if limit is not None:
|
|
27
|
+
params["limit"] = limit
|
|
28
|
+
if status is not None:
|
|
29
|
+
params["status"] = str(status.value if isinstance(status, AMPSOrderStatus) else status)
|
|
30
|
+
|
|
31
|
+
raw = await self._http.get(f"/v1/sites/{site_id}/orders", params or None)
|
|
32
|
+
response = PaginatedOrderResponse.model_validate(raw)
|
|
33
|
+
return {
|
|
34
|
+
"orders": [o.model_dump() for o in response.data],
|
|
35
|
+
"total": response.pagination.total,
|
|
36
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional, Union, List
|
|
3
|
+
|
|
4
|
+
from ..http import HttpClient
|
|
5
|
+
from ..types.amps import AMPSProduct, PaginatedProductResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProductsModule:
|
|
9
|
+
def __init__(self, http: HttpClient) -> None:
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
async def search(
|
|
13
|
+
self,
|
|
14
|
+
site_id: str,
|
|
15
|
+
q: Optional[str] = None,
|
|
16
|
+
category: Optional[str] = None,
|
|
17
|
+
min_price: Optional[float] = None,
|
|
18
|
+
max_price: Optional[float] = None,
|
|
19
|
+
in_stock: Optional[bool] = None,
|
|
20
|
+
page: Optional[int] = None,
|
|
21
|
+
limit: Optional[int] = None,
|
|
22
|
+
) -> dict:
|
|
23
|
+
params: dict = {}
|
|
24
|
+
if q is not None:
|
|
25
|
+
params["q"] = q
|
|
26
|
+
if category is not None:
|
|
27
|
+
params["category"] = category
|
|
28
|
+
if min_price is not None:
|
|
29
|
+
params["min_price"] = min_price
|
|
30
|
+
if max_price is not None:
|
|
31
|
+
params["max_price"] = max_price
|
|
32
|
+
if in_stock is not None:
|
|
33
|
+
params["in_stock"] = in_stock
|
|
34
|
+
if page is not None:
|
|
35
|
+
params["page"] = page
|
|
36
|
+
if limit is not None:
|
|
37
|
+
params["limit"] = limit
|
|
38
|
+
|
|
39
|
+
raw = await self._http.get(f"/v1/sites/{site_id}/products", params or None)
|
|
40
|
+
response = PaginatedProductResponse.model_validate(raw)
|
|
41
|
+
pg = response.pagination
|
|
42
|
+
total_pages = (pg.total + pg.limit - 1) // pg.limit if pg.limit > 0 else 1
|
|
43
|
+
return {
|
|
44
|
+
"products": [p.model_dump() for p in response.data],
|
|
45
|
+
"total": pg.total,
|
|
46
|
+
"page": pg.page,
|
|
47
|
+
"total_pages": total_pages,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async def get_by_id(self, site_id: str, product_id: str) -> AMPSProduct:
|
|
51
|
+
raw = await self._http.get(f"/v1/sites/{site_id}/products/{product_id}")
|
|
52
|
+
return AMPSProduct.model_validate(raw)
|
|
53
|
+
|
|
54
|
+
async def compare(
|
|
55
|
+
self,
|
|
56
|
+
site_ids: List[str],
|
|
57
|
+
query: str,
|
|
58
|
+
min_price: Optional[float] = None,
|
|
59
|
+
max_price: Optional[float] = None,
|
|
60
|
+
in_stock: Optional[bool] = None,
|
|
61
|
+
) -> dict:
|
|
62
|
+
body: dict = {"query": query, "site_ids": site_ids}
|
|
63
|
+
filters: dict = {}
|
|
64
|
+
if min_price is not None:
|
|
65
|
+
filters["min_price"] = min_price
|
|
66
|
+
if max_price is not None:
|
|
67
|
+
filters["max_price"] = max_price
|
|
68
|
+
if in_stock is not None:
|
|
69
|
+
filters["in_stock"] = in_stock
|
|
70
|
+
if filters:
|
|
71
|
+
body["filters"] = filters
|
|
72
|
+
|
|
73
|
+
return await self._http.post("/v1/multi/products/compare", body)
|