beckn-sdk 1.0.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.
- beckn_sdk-1.0.0/PKG-INFO +9 -0
- beckn_sdk-1.0.0/README.md +347 -0
- beckn_sdk-1.0.0/beckn/__init__.py +40 -0
- beckn_sdk-1.0.0/beckn/client.py +288 -0
- beckn_sdk-1.0.0/beckn/types.py +651 -0
- beckn_sdk-1.0.0/beckn_sdk.egg-info/PKG-INFO +9 -0
- beckn_sdk-1.0.0/beckn_sdk.egg-info/SOURCES.txt +11 -0
- beckn_sdk-1.0.0/beckn_sdk.egg-info/dependency_links.txt +1 -0
- beckn_sdk-1.0.0/beckn_sdk.egg-info/requires.txt +5 -0
- beckn_sdk-1.0.0/beckn_sdk.egg-info/top_level.txt +1 -0
- beckn_sdk-1.0.0/pyproject.toml +20 -0
- beckn_sdk-1.0.0/setup.cfg +4 -0
- beckn_sdk-1.0.0/tests/test_sdk.py +175 -0
beckn_sdk-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: beckn-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: BeckN Protocol SDK for Python
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: httpx>=0.27.0
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
9
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# BeckN Protocol SDK — Python
|
|
2
|
+
|
|
3
|
+
Python SDK for the BeckN Protocol, implementing the Beckn protocol specification for domain-agnostic digital commerce.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install beckn-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Initialize Client
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from beckn import BeckNClient, BeckNClientConfig
|
|
17
|
+
|
|
18
|
+
client = BeckNClient(BeckNClientConfig(
|
|
19
|
+
base_url="https://api.beckn.network/v1",
|
|
20
|
+
api_key="bk_your_api_key_here",
|
|
21
|
+
timeout=30000,
|
|
22
|
+
))
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Register a BAP
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
bap = client.create_bap(BapCreate(
|
|
29
|
+
id="my-bap-001",
|
|
30
|
+
name="My Store App",
|
|
31
|
+
endpoint="https://store.example.com",
|
|
32
|
+
country="US",
|
|
33
|
+
lat=40.7128,
|
|
34
|
+
lon=-74.0060,
|
|
35
|
+
))
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Register a BPP
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
bpp = client.create_bpp(BppCreate(
|
|
42
|
+
id="my-bpp-001",
|
|
43
|
+
name="My Provider App",
|
|
44
|
+
endpoint="https://provider.example.com",
|
|
45
|
+
country="US",
|
|
46
|
+
currency="USD",
|
|
47
|
+
lat=40.7128,
|
|
48
|
+
lon=-74.0060,
|
|
49
|
+
))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Create an Order
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
order = client.create_order(OrderCreate(
|
|
56
|
+
id="order-001",
|
|
57
|
+
transaction_id="txn-001",
|
|
58
|
+
bap_id="my-bap-001",
|
|
59
|
+
bpp_id="my-bpp-001",
|
|
60
|
+
))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Discover Nearby BPPs (GeoDNS)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# Find BPPs in a specific country
|
|
67
|
+
bpps = client.discover_nearest_bap(country="US")
|
|
68
|
+
|
|
69
|
+
# Find BPPs near a geographic location
|
|
70
|
+
nearby = client.discover_marketplace(lat=40.7128, lng=-74.0060, radius_km=50, limit=10)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### GBP (Google Business Profile)
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# Register a GBP account
|
|
77
|
+
account = client.create_gbp_account(GbpAccountCreate(
|
|
78
|
+
email="merchant@example.com",
|
|
79
|
+
account_name="accounts/1234567890",
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
# Sync GBP locations as BPPs (auto-registers A2A agent cards)
|
|
83
|
+
synced = client.sync_gbp_locations(account.id)
|
|
84
|
+
print(f"{len(synced['bpps'])} BPPs synced")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### A2A Agent Discovery
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# Discover agents near a location (cross-protocol with GeoDNS)
|
|
91
|
+
nearby = client.discover_marketplace(lat=40.7128, lng=-74.0060, radius_km=50, limit=10)
|
|
92
|
+
|
|
93
|
+
# Register an agent card
|
|
94
|
+
agent = client.register_agent_card(AgentCardRegister(
|
|
95
|
+
agent_id="agent-001",
|
|
96
|
+
name="Travel Booking Agent",
|
|
97
|
+
url="https://travel.example.com",
|
|
98
|
+
capabilities={"streaming": True},
|
|
99
|
+
skills=[{"id": "search", "name": "Travel Search"}],
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
# Discover agents by skill
|
|
103
|
+
results = client.discover_agents(skill="booking")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### ANP (Agent Network Protocol)
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# Announce a DID to the network
|
|
110
|
+
announcement = client.announce(AnpAnnouncementCreate(
|
|
111
|
+
announcement_id="ann-001",
|
|
112
|
+
did="did:example:123",
|
|
113
|
+
service_endpoint="https://agent.example.com",
|
|
114
|
+
))
|
|
115
|
+
|
|
116
|
+
# Register a witness node
|
|
117
|
+
witness = client.register_witness(AnpWitnessRegister(
|
|
118
|
+
witness_id="witness-001",
|
|
119
|
+
did="did:example:witness-1",
|
|
120
|
+
endpoint="https://witness.example.com",
|
|
121
|
+
protocols=["gossip", "http"],
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
# Verify a DID document
|
|
125
|
+
verification = client.verify(AnpVerificationVerify(
|
|
126
|
+
did="did:example:123",
|
|
127
|
+
did_document={"id": "did:example:123"},
|
|
128
|
+
method="key",
|
|
129
|
+
))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### ACP (Agent Communication Protocol)
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# Register a credential issuer
|
|
136
|
+
issuer = client.register_issuer(AcpIssuerRegister(
|
|
137
|
+
issuer_id="issuer-001",
|
|
138
|
+
name="Test Issuer",
|
|
139
|
+
authorization_endpoint="https://issuer.example.com/authorize",
|
|
140
|
+
token_endpoint="https://issuer.example.com/token",
|
|
141
|
+
jwks_uri="https://issuer.example.com/.well-known/jwks.json",
|
|
142
|
+
))
|
|
143
|
+
|
|
144
|
+
# Issue a token
|
|
145
|
+
token = client.issue_token(AcpTokenIssue(
|
|
146
|
+
token_value="eyJhbGciOiJSUzI1NiIs...",
|
|
147
|
+
subject="did:example:subject-1",
|
|
148
|
+
))
|
|
149
|
+
|
|
150
|
+
# Submit a verifiable presentation
|
|
151
|
+
presentation = client.submit_presentation(AcpPresentationSubmit(
|
|
152
|
+
presentation_id="vp-123",
|
|
153
|
+
holder_did="did:example:holder-1",
|
|
154
|
+
issuer_id="issuer-001",
|
|
155
|
+
claims={"age": 25},
|
|
156
|
+
))
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Google Business Profile Sync
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Register a GBP account
|
|
163
|
+
account = client.create_gbp_account({
|
|
164
|
+
"email": "merchant@example.com",
|
|
165
|
+
"account_name": "accounts/1234567890",
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
# Sync GBP locations as BPPs (auto-registers A2A agent cards)
|
|
169
|
+
result = client.sync_gbp_locations(account["id"])
|
|
170
|
+
print(f"{len(result['bpps'])} BPPs synced")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### A2A Agent Discovery
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Discover nearby BPPs via GeoDNS (maps to A2A agents)
|
|
177
|
+
bpps = client.discover_nearest_bpp(country="US")
|
|
178
|
+
|
|
179
|
+
# Register an agent card
|
|
180
|
+
agent_card = client.register_agent_card({
|
|
181
|
+
"agent_id": "agent-001",
|
|
182
|
+
"name": "Travel Booking Agent",
|
|
183
|
+
"url": "https://travel.example.com",
|
|
184
|
+
"capabilities": {"streaming": True},
|
|
185
|
+
"skills": [{"id": "search", "name": "Travel Search"}],
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
# Send a message to a task
|
|
189
|
+
message = client.send_message({
|
|
190
|
+
"message_id": "msg-001",
|
|
191
|
+
"context_id": "ctx-1",
|
|
192
|
+
"role": "user",
|
|
193
|
+
"parts": [{"type": "text", "text": "Find flights to NYC"}],
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### ANP (Agent Network Protocol)
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
# Announce a DID to the network
|
|
201
|
+
announcement = client.announce({
|
|
202
|
+
"announcement_id": "ann-001",
|
|
203
|
+
"did": "did:example:123",
|
|
204
|
+
"service_endpoint": "https://agent.example.com",
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
# Register a witness node
|
|
208
|
+
witness = client.register_witness({
|
|
209
|
+
"witness_id": "witness-001",
|
|
210
|
+
"did": "did:example:witness-1",
|
|
211
|
+
"endpoint": "https://witness.example.com",
|
|
212
|
+
"protocols": ["gossip", "http"],
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### ACP (Agent Communication Protocol)
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
# Register a credential issuer
|
|
220
|
+
issuer = client.register_issuer({
|
|
221
|
+
"issuer_id": "issuer-001",
|
|
222
|
+
"name": "Test Issuer",
|
|
223
|
+
"authorization_endpoint": "https://issuer.example.com/authorize",
|
|
224
|
+
"token_endpoint": "https://issuer.example.com/token",
|
|
225
|
+
"jwks_uri": "https://issuer.example.com/.well-known/jwks.json",
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
# Issue a token
|
|
229
|
+
token = client.issue_token({
|
|
230
|
+
"token_value": "eyJhbGciOiJSUzI1NiIs...",
|
|
231
|
+
"subject": "did:example:subject-1",
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
# Submit a verifiable presentation
|
|
235
|
+
presentation = client.submit_presentation({
|
|
236
|
+
"id": "pres-001",
|
|
237
|
+
"presentation_id": "vp-123",
|
|
238
|
+
"vp": {"@context": ["..."], "type": "VerifiablePresentation"},
|
|
239
|
+
"credential_issuer_id": "issuer-001",
|
|
240
|
+
"holder": "did:example:holder-1",
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## API Reference
|
|
245
|
+
|
|
246
|
+
### BeckNClientConfig
|
|
247
|
+
|
|
248
|
+
| Field | Type | Default | Description |
|
|
249
|
+
|-------|------|---------|-------------|
|
|
250
|
+
| `base_url` | `str` | `'http://localhost:4000/v1'` | API base URL |
|
|
251
|
+
| `api_key` | `Optional[str]` | `None` | API key for authentication |
|
|
252
|
+
| `timeout` | `int` | `30000` | Request timeout in milliseconds |
|
|
253
|
+
|
|
254
|
+
### Methods
|
|
255
|
+
|
|
256
|
+
#### Orders
|
|
257
|
+
- `list_orders() -> list[Order]`
|
|
258
|
+
- `create_order(data: OrderCreate) -> Order`
|
|
259
|
+
- `get_order(id: str) -> Order`
|
|
260
|
+
- `delete_order(id: str) -> None`
|
|
261
|
+
|
|
262
|
+
#### BAPs
|
|
263
|
+
- `list_baps() -> list[Bap]`
|
|
264
|
+
- `list_baps_by_company(company_id: str) -> list[Bap]`
|
|
265
|
+
- `list_baps_by_country(country: str) -> list[Bap]`
|
|
266
|
+
- `list_baps_nearby(lat: float, lng: float, radius_km: int = 50) -> list[Bap]`
|
|
267
|
+
- `create_bap(data: BapCreate) -> Bap`
|
|
268
|
+
- `get_bap(bap_id: str) -> Bap`
|
|
269
|
+
- `update_bap(bap_id: str, data: BapUpdate) -> Bap`
|
|
270
|
+
|
|
271
|
+
#### BPPs
|
|
272
|
+
- `list_bpps() -> list[Bpp]`
|
|
273
|
+
- `list_bpps_by_company(company_id: str) -> list[Bpp]`
|
|
274
|
+
- `list_bpps_by_country(country: str) -> list[Bpp]`
|
|
275
|
+
- `list_bpps_nearby(lat: float, lng: float, radius_km: int = 50) -> list[Bpp]`
|
|
276
|
+
- `create_bpp(data: BppCreate) -> Bpp`
|
|
277
|
+
- `get_bpp(bpp_id: str) -> Bpp`
|
|
278
|
+
- `update_bpp(bpp_id: str, data: BppUpdate) -> Bpp`
|
|
279
|
+
|
|
280
|
+
#### Companies (B2B Multi-Tenant)
|
|
281
|
+
- `list_companies() -> list[Company]`
|
|
282
|
+
- `create_company(data: CompanyCreate) -> Company`
|
|
283
|
+
- `get_company(company_id: str) -> Company`
|
|
284
|
+
- `list_companies_by_domain(domain: str) -> list[Company]`
|
|
285
|
+
|
|
286
|
+
#### API Keys
|
|
287
|
+
- `list_api_keys() -> list[ApiKey]`
|
|
288
|
+
- `create_api_key(data: ApiKeyCreate) -> ApiKeyResponse`
|
|
289
|
+
- `verify_api_key(secret: str) -> ApiKeyVerifyResponse`
|
|
290
|
+
- `revoke_api_key(key_id: str) -> ApiKey`
|
|
291
|
+
- `rotate_api_key(key_id: str) -> ApiKeyResponse`
|
|
292
|
+
|
|
293
|
+
#### GeoDNS Discovery
|
|
294
|
+
- `discover_nearest_bap(country: str = None, city: str = None) -> GeoDnsResult`
|
|
295
|
+
- `discover_nearest_bpp(country: str = None, city: str = None) -> GeoDnsResult`
|
|
296
|
+
- `discover_marketplace(lat: float, lng: float, radius_km: int = 50, limit: int = 10) -> list[GeoDnsResult]`
|
|
297
|
+
|
|
298
|
+
#### GBP (Google Business Profile)
|
|
299
|
+
- `list_gbp_accounts() -> list[GbpAccount]`
|
|
300
|
+
- `create_gbp_account(data: dict) -> GbpAccount`
|
|
301
|
+
- `sync_gbp_locations(account_id: str) -> dict`
|
|
302
|
+
|
|
303
|
+
#### A2A (Agent-to-Agent)
|
|
304
|
+
- `list_agent_cards() -> list[AgentCard]`
|
|
305
|
+
- `register_agent_card(data: AgentCardRegister) -> AgentCard`
|
|
306
|
+
- `list_tasks() -> list[A2ATask]`
|
|
307
|
+
- `create_task(data: A2ATaskCreate) -> A2ATask`
|
|
308
|
+
- `send_message(data: A2ATaskMessageSend) -> A2ATaskMessage`
|
|
309
|
+
- `list_messages() -> list[A2ATaskMessage]`
|
|
310
|
+
- `list_artifacts() -> list[A2AArtifact]`
|
|
311
|
+
- `create_artifact(data: A2AArtifactCreate) -> A2AArtifact`
|
|
312
|
+
|
|
313
|
+
#### MCP (Model Context Protocol)
|
|
314
|
+
- `list_tools() -> list[McpTool]`
|
|
315
|
+
- `create_tool(data: McpToolCreate) -> McpTool`
|
|
316
|
+
- `list_resources() -> list[McpResource]`
|
|
317
|
+
- `create_resource(data: McpResourceCreate) -> McpResource`
|
|
318
|
+
- `list_prompts() -> list[McpPrompt]`
|
|
319
|
+
- `create_prompt(data: McpPromptCreate) -> McpPrompt`
|
|
320
|
+
|
|
321
|
+
#### ACP (Agent Communication Protocol)
|
|
322
|
+
- `list_issuers() -> list[AcpIssuer]`
|
|
323
|
+
- `register_issuer(data: AcpIssuerRegister) -> AcpIssuer`
|
|
324
|
+
- `list_tokens() -> list[AcpToken]`
|
|
325
|
+
- `issue_token(data: AcpTokenIssue) -> AcpToken`
|
|
326
|
+
- `introspect_token(token: str) -> AcpToken`
|
|
327
|
+
- `list_presentations() -> list[AcpPresentation]`
|
|
328
|
+
- `submit_presentation(data: AcpPresentationSubmit) -> AcpPresentation`
|
|
329
|
+
- `list_policies() -> list[AcpAccessPolicy]`
|
|
330
|
+
- `create_policy(data: AcpAccessPolicyCreate) -> AcpAccessPolicy`
|
|
331
|
+
|
|
332
|
+
#### ANP (Agent Network Protocol)
|
|
333
|
+
- `list_announcements() -> list[AnpAnnouncement]`
|
|
334
|
+
- `announce(data: AnpAnnouncementCreate) -> AnpAnnouncement`
|
|
335
|
+
- `list_witnesses() -> list[AnpWitness]`
|
|
336
|
+
- `register_witness(data: AnpWitnessRegister) -> AnpWitness`
|
|
337
|
+
- `verify(data: AnpVerificationVerify) -> AnpVerification`
|
|
338
|
+
|
|
339
|
+
#### Subscriptions
|
|
340
|
+
- `list_subscriptions() -> list[Subscription]`
|
|
341
|
+
- `create_subscription(data: SubscriptionCreate) -> Subscription`
|
|
342
|
+
- `cancel_subscription(sub_id: str) -> Subscription`
|
|
343
|
+
- `renew_subscription(sub_id: str) -> Subscription`
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
Apache-2.0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""BeckN Protocol SDK."""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.0.0"
|
|
4
|
+
|
|
5
|
+
from .client import BeckNClient, BeckNError
|
|
6
|
+
from .types import (
|
|
7
|
+
Bap, BapCreate, BapUpdate, Bpp, BppCreate, BppUpdate,
|
|
8
|
+
Order, OrderCreate, OrderUpdate, OrderState, Item, ItemCreate,
|
|
9
|
+
Provider, ProviderCreate, Fulfillment, FulfillmentCreate,
|
|
10
|
+
Subscription, SubscriptionCreate, SubscriptionStatus, SubscriberType,
|
|
11
|
+
ApiKey, ApiKeyCreate, ApiKeyResponse, ApiKeyVerifyRequest, ApiKeyVerifyResponse,
|
|
12
|
+
GeoLocation, GeoDnsResult, GeoDiscoveryConfig,
|
|
13
|
+
AgentCard, AgentCardRegister, A2ATask, A2ATaskCreate, TaskStatus,
|
|
14
|
+
A2ATaskMessage, A2ATaskMessageSend, A2AArtifact, A2AArtifactCreate,
|
|
15
|
+
McpTool, McpToolCreate, McpResource, McpResourceCreate,
|
|
16
|
+
McpPrompt, McpPromptCreate, McpClientType, McpClientRegister, TransportType,
|
|
17
|
+
AcpIssuer, AcpIssuerRegister, AcpToken, AcpTokenIssue,
|
|
18
|
+
AcpPresentation, AcpPresentationSubmit, AcpAccessPolicy, AcpAccessPolicyCreate,
|
|
19
|
+
AnpAnnouncement, AnpAnnouncementCreate, AnpWitness, AnpWitnessRegister,
|
|
20
|
+
AnpVerification, AnpVerificationVerify, MessageRole,
|
|
21
|
+
BeckNClientConfig,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"BeckNClient", "BeckNError", "BeckNClientConfig",
|
|
26
|
+
"Bap", "BapCreate", "BapUpdate", "Bpp", "BppCreate", "BppUpdate",
|
|
27
|
+
"Order", "OrderCreate", "OrderUpdate", "OrderState", "Item", "ItemCreate",
|
|
28
|
+
"Provider", "ProviderCreate", "Fulfillment", "FulfillmentCreate",
|
|
29
|
+
"Subscription", "SubscriptionCreate", "SubscriptionStatus", "SubscriberType",
|
|
30
|
+
"ApiKey", "ApiKeyCreate", "ApiKeyResponse", "ApiKeyVerifyRequest", "ApiKeyVerifyResponse",
|
|
31
|
+
"GeoLocation", "GeoDnsResult", "GeoDiscoveryConfig",
|
|
32
|
+
"AgentCard", "AgentCardRegister", "A2ATask", "A2ATaskCreate", "TaskStatus",
|
|
33
|
+
"A2ATaskMessage", "A2ATaskMessageSend", "A2AArtifact", "A2AArtifactCreate",
|
|
34
|
+
"McpTool", "McpToolCreate", "McpResource", "McpResourceCreate",
|
|
35
|
+
"McpPrompt", "McpPromptCreate", "McpClientType", "McpClientRegister", "TransportType",
|
|
36
|
+
"AcpIssuer", "AcpIssuerRegister", "AcpToken", "AcpTokenIssue",
|
|
37
|
+
"AcpPresentation", "AcpPresentationSubmit", "AcpAccessPolicy", "AcpAccessPolicyCreate",
|
|
38
|
+
"AnpAnnouncement", "AnpAnnouncementCreate", "AnpWitness", "AnpWitnessRegister",
|
|
39
|
+
"AnpVerification", "AnpVerificationVerify", "MessageRole",
|
|
40
|
+
]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Client module for the BeckN Protocol SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any, Optional, TypeVar
|
|
8
|
+
from dataclasses import asdict
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .types import (
|
|
13
|
+
Bap, BapCreate, BapUpdate, Bpp, BppCreate, BppUpdate,
|
|
14
|
+
Order, OrderCreate, OrderUpdate, Item, ItemCreate,
|
|
15
|
+
Provider, ProviderCreate, Fulfillment, FulfillmentCreate,
|
|
16
|
+
Subscription, SubscriptionCreate,
|
|
17
|
+
ApiKey, ApiKeyCreate, ApiKeyResponse, ApiKeyVerifyRequest, ApiKeyVerifyResponse,
|
|
18
|
+
ErrorResponse, GeoDnsResult, GeoLocation,
|
|
19
|
+
AgentCard, AgentCardRegister, A2ATask, A2ATaskCreate,
|
|
20
|
+
A2ATaskMessage, A2ATaskMessageSend, A2AArtifact, A2AArtifactCreate,
|
|
21
|
+
McpTool, McpToolCreate, McpResource, McpResourceCreate,
|
|
22
|
+
McpPrompt, McpPromptCreate, McpClientType, McpClientRegister,
|
|
23
|
+
AcpIssuer, AcpIssuerRegister, AcpToken, AcpTokenIssue,
|
|
24
|
+
AcpPresentation, AcpPresentationSubmit, AcpAccessPolicy, AcpAccessPolicyCreate,
|
|
25
|
+
AnpAnnouncement, AnpAnnouncementCreate, AnpWitness, AnpWitnessRegister,
|
|
26
|
+
AnpVerification, AnpVerificationVerify,
|
|
27
|
+
Company, CompanyCreate, BeckNClientConfig,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
T = TypeVar('T')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BeckNError(Exception):
|
|
34
|
+
def __init__(self, message: str, status: int = 500, response: Optional[ErrorResponse] = None):
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.status = status
|
|
37
|
+
self.response = response
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _to_dict(obj: Any) -> dict[str, Any]:
|
|
41
|
+
if isinstance(obj, dict):
|
|
42
|
+
return obj
|
|
43
|
+
if hasattr(obj, '__dict__'):
|
|
44
|
+
return {k: v for k, v in obj.__dict__.items() if v is not None}
|
|
45
|
+
return asdict(obj)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _from_dict(cls: type, data: dict[str, Any]) -> Any:
|
|
49
|
+
if hasattr(cls, '__dataclass_fields__'):
|
|
50
|
+
field_names = set(cls.__dataclass_fields__.keys())
|
|
51
|
+
filtered = {k: v for k, v in data.items() if k in field_names}
|
|
52
|
+
return cls(**filtered)
|
|
53
|
+
return data
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BeckNClient:
|
|
57
|
+
def __init__(self, config: Optional[BeckNClientConfig] = None):
|
|
58
|
+
if config is None:
|
|
59
|
+
config = BeckNClientConfig()
|
|
60
|
+
self.base_url = config.base_url or os.environ.get('BECKN_API_URL', 'http://localhost:4000/v1')
|
|
61
|
+
self.api_key = config.api_key or os.environ.get('BECKN_API_KEY')
|
|
62
|
+
self.timeout = config.timeout or 30
|
|
63
|
+
self._client = httpx.Client(timeout=self.timeout, base_url=self.base_url)
|
|
64
|
+
self._async_client = httpx.AsyncClient(timeout=self.timeout, base_url=self.base_url)
|
|
65
|
+
|
|
66
|
+
def _headers(self) -> dict[str, str]:
|
|
67
|
+
h = {'Content-Type': 'application/json', 'Accept': 'application/json'}
|
|
68
|
+
if self.api_key:
|
|
69
|
+
h['x-api-key'] = self.api_key
|
|
70
|
+
return h
|
|
71
|
+
|
|
72
|
+
def _request(self, method: str, path: str, body: Optional[dict] = None) -> dict[str, Any]:
|
|
73
|
+
kwargs = {'headers': self._headers()}
|
|
74
|
+
if body is not None:
|
|
75
|
+
kwargs['content'] = json.dumps(body)
|
|
76
|
+
resp = self._client.request(method, path, **kwargs)
|
|
77
|
+
if not resp.is_success:
|
|
78
|
+
try:
|
|
79
|
+
err = resp.json()
|
|
80
|
+
except Exception:
|
|
81
|
+
err = {'message': resp.text}
|
|
82
|
+
raise BeckNError(err.get('message', f'HTTP {resp.status_code}'), resp.status_code, _from_dict(ErrorResponse, err))
|
|
83
|
+
if resp.status_code == 204:
|
|
84
|
+
return None
|
|
85
|
+
return resp.json()
|
|
86
|
+
|
|
87
|
+
# === BeckN Orders ===
|
|
88
|
+
def list_orders(self) -> list[Order]:
|
|
89
|
+
return [Order(**o) for o in self._request('GET', '/orders')]
|
|
90
|
+
|
|
91
|
+
def create_order(self, data: OrderCreate) -> Order:
|
|
92
|
+
return Order(**self._request('POST', '/orders', _to_dict(data)))
|
|
93
|
+
|
|
94
|
+
def get_order(self, order_id: str) -> Order:
|
|
95
|
+
return Order(**self._request('GET', f'/orders/{order_id}'))
|
|
96
|
+
|
|
97
|
+
def update_order(self, order_id: str, data: OrderUpdate) -> Order:
|
|
98
|
+
return Order(**self._request('PATCH', f'/orders/{order_id}', _to_dict(data)))
|
|
99
|
+
|
|
100
|
+
def delete_order(self, order_id: str) -> None:
|
|
101
|
+
self._request('DELETE', f'/orders/{order_id}')
|
|
102
|
+
|
|
103
|
+
# === BAPs ===
|
|
104
|
+
def list_baps(self) -> list[Bap]:
|
|
105
|
+
return [Bap(**b) for b in self._request('GET', '/baps')]
|
|
106
|
+
|
|
107
|
+
def list_baps_by_company(self, company_id: str) -> list[Bap]:
|
|
108
|
+
return [Bap(**b) for b in self._request('GET', f'/baps?company_id={company_id}')]
|
|
109
|
+
|
|
110
|
+
def create_bap(self, data: BapCreate) -> Bap:
|
|
111
|
+
return Bap(**self._request('POST', '/baps', _to_dict(data)))
|
|
112
|
+
|
|
113
|
+
def get_bap(self, bap_id: str) -> Bap:
|
|
114
|
+
return Bap(**self._request('GET', f'/baps/{bap_id}'))
|
|
115
|
+
|
|
116
|
+
def update_bap(self, bap_id: str, data: BapUpdate) -> Bap:
|
|
117
|
+
return Bap(**self._request('PATCH', f'/baps/{bap_id}', _to_dict(data)))
|
|
118
|
+
|
|
119
|
+
# === BPPs ===
|
|
120
|
+
def list_bpps(self) -> list[Bpp]:
|
|
121
|
+
return [Bpp(**b) for b in self._request('GET', '/bpps')]
|
|
122
|
+
|
|
123
|
+
def list_bpps_by_company(self, company_id: str) -> list[Bpp]:
|
|
124
|
+
return [Bpp(**b) for b in self._request('GET', f'/bpps?company_id={company_id}')]
|
|
125
|
+
|
|
126
|
+
def create_bpp(self, data: BppCreate) -> Bpp:
|
|
127
|
+
return Bpp(**self._request('POST', '/bpps', _to_dict(data)))
|
|
128
|
+
|
|
129
|
+
def get_bpp(self, bpp_id: str) -> Bpp:
|
|
130
|
+
return Bpp(**self._request('GET', f'/bpps/{bpp_id}'))
|
|
131
|
+
|
|
132
|
+
def update_bpp(self, bpp_id: str, data: BppUpdate) -> Bpp:
|
|
133
|
+
return Bpp(**self._request('PATCH', f'/bpps/{bpp_id}', _to_dict(data)))
|
|
134
|
+
|
|
135
|
+
# === Items ===
|
|
136
|
+
def list_items(self) -> list[Item]:
|
|
137
|
+
return [Item(**i) for i in self._request('GET', '/items')]
|
|
138
|
+
|
|
139
|
+
def create_item(self, data: ItemCreate) -> Item:
|
|
140
|
+
return Item(**self._request('POST', '/items', _to_dict(data)))
|
|
141
|
+
|
|
142
|
+
def get_item(self, item_id: str) -> Item:
|
|
143
|
+
return Item(**self._request('GET', f'/items/{item_id}'))
|
|
144
|
+
|
|
145
|
+
# === Providers ===
|
|
146
|
+
def list_providers(self) -> list[Provider]:
|
|
147
|
+
return [Provider(**p) for p in self._request('GET', '/providers')]
|
|
148
|
+
|
|
149
|
+
def create_provider(self, data: ProviderCreate) -> Provider:
|
|
150
|
+
return Provider(**self._request('POST', '/providers', _to_dict(data)))
|
|
151
|
+
|
|
152
|
+
def get_provider(self, provider_id: str) -> Provider:
|
|
153
|
+
return Provider(**self._request('GET', f'/providers/{provider_id}'))
|
|
154
|
+
|
|
155
|
+
def delete_provider(self, provider_id: str) -> None:
|
|
156
|
+
self._request('DELETE', f'/providers/{provider_id}')
|
|
157
|
+
|
|
158
|
+
# === Fulfillments ===
|
|
159
|
+
def list_fulfillments(self) -> list[Fulfillment]:
|
|
160
|
+
return [Fulfillment(**f) for f in self._request('GET', '/fulfillments')]
|
|
161
|
+
|
|
162
|
+
def create_fulfillment(self, data: FulfillmentCreate) -> Fulfillment:
|
|
163
|
+
return Fulfillment(**self._request('POST', '/fulfillments', _to_dict(data)))
|
|
164
|
+
|
|
165
|
+
def track_fulfillment(self, tracking_id: str) -> list[Fulfillment]:
|
|
166
|
+
return [Fulfillment(**f) for f in self._request('POST', '/fulfillments/track', {'tracking_id': tracking_id})]
|
|
167
|
+
|
|
168
|
+
# === Subscriptions ===
|
|
169
|
+
def list_subscriptions(self) -> list[Subscription]:
|
|
170
|
+
return [Subscription(**s) for s in self._request('GET', '/subscriptions')]
|
|
171
|
+
|
|
172
|
+
def create_subscription(self, data: SubscriptionCreate) -> Subscription:
|
|
173
|
+
return Subscription(**self._request('POST', '/subscriptions', _to_dict(data)))
|
|
174
|
+
|
|
175
|
+
def get_subscription(self, sub_id: str) -> Subscription:
|
|
176
|
+
return Subscription(**self._request('GET', f'/subscriptions/{sub_id}'))
|
|
177
|
+
|
|
178
|
+
def cancel_subscription(self, sub_id: str) -> Subscription:
|
|
179
|
+
return Subscription(**self._request('POST', f'/subscriptions/{sub_id}/cancel', {}))
|
|
180
|
+
|
|
181
|
+
def renew_subscription(self, sub_id: str) -> Subscription:
|
|
182
|
+
return Subscription(**self._request('POST', f'/subscriptions/{sub_id}/renew', {}))
|
|
183
|
+
|
|
184
|
+
# === API Keys ===
|
|
185
|
+
def list_api_keys(self) -> list[ApiKey]:
|
|
186
|
+
return [ApiKey(**k) for k in self._request('GET', '/api-keys')]
|
|
187
|
+
|
|
188
|
+
def create_api_key(self, data: ApiKeyCreate) -> ApiKeyResponse:
|
|
189
|
+
return ApiKeyResponse(**self._request('POST', '/api-keys', _to_dict(data)))
|
|
190
|
+
|
|
191
|
+
def get_api_key(self, key_id: str) -> ApiKey:
|
|
192
|
+
return ApiKey(**self._request('GET', f'/api-keys/{key_id}'))
|
|
193
|
+
|
|
194
|
+
def verify_api_key(self, secret: str) -> ApiKeyVerifyResponse:
|
|
195
|
+
resp = self._request('POST', '/api-keys/verify', {'secret': secret})
|
|
196
|
+
return ApiKeyVerifyResponse(**resp)
|
|
197
|
+
|
|
198
|
+
def revoke_api_key(self, key_id: str) -> ApiKey:
|
|
199
|
+
return ApiKey(**self._request('POST', f'/api-keys/{key_id}/revoke', {}))
|
|
200
|
+
|
|
201
|
+
def rotate_api_key(self, key_id: str) -> ApiKeyResponse:
|
|
202
|
+
return ApiKeyResponse(**self._request('POST', f'/api-keys/{key_id}/rotate', {}))
|
|
203
|
+
|
|
204
|
+
def list_api_keys_by_owner(self, owner_id: str) -> list[ApiKey]:
|
|
205
|
+
return [ApiKey(**k) for k in self._request('GET', f'/api-keys/owner/{owner_id}')]
|
|
206
|
+
|
|
207
|
+
# === GeoDNS Discovery ===
|
|
208
|
+
def discover_nearest_bap(self, country: Optional[str] = None, city: Optional[str] = None) -> GeoDnsResult:
|
|
209
|
+
from urllib.parse import urlencode
|
|
210
|
+
params = []
|
|
211
|
+
if country: params.append(f'country={country}')
|
|
212
|
+
if city: params.append(f'city={city}')
|
|
213
|
+
query = urlencode({'country': country or '', 'city': city or ''})
|
|
214
|
+
resp = self._request('GET', f'/geodns/baps?{query}')
|
|
215
|
+
loc = GeoLocation(**resp['location'])
|
|
216
|
+
return GeoDnsResult(**resp, location=loc)
|
|
217
|
+
|
|
218
|
+
def discover_nearest_bpp(self, country: Optional[str] = None, city: Optional[str] = None) -> GeoDnsResult:
|
|
219
|
+
from urllib.parse import urlencode
|
|
220
|
+
query = urlencode({'country': country or '', 'city': city or ''})
|
|
221
|
+
resp = self._request('GET', f'/geodns/bpps?{query}')
|
|
222
|
+
loc = GeoLocation(**resp['location'])
|
|
223
|
+
return GeoDnsResult(**resp, location=loc)
|
|
224
|
+
|
|
225
|
+
def discover_marketplace(self, lat: float, lng: float, radius_km: int = 50, limit: int = 10) -> list[GeoDnsResult]:
|
|
226
|
+
resp = self._request('GET', f'/geodns/marketplaces?lat={lat}&lng={lng}&radius_km={radius_km}&limit={limit}')
|
|
227
|
+
return [GeoDnsResult(**r, location=GeoLocation(**r['location'])) for r in resp]
|
|
228
|
+
|
|
229
|
+
# === A2A ===
|
|
230
|
+
def list_agent_cards(self) -> list[AgentCard]:
|
|
231
|
+
return [AgentCard(**a) for a in self._request('GET', '/a2a/agent-cards')]
|
|
232
|
+
|
|
233
|
+
def register_agent_card(self, data: AgentCardRegister) -> AgentCard:
|
|
234
|
+
return AgentCard(**self._request('POST', '/a2a/agent-cards', _to_dict(data)))
|
|
235
|
+
|
|
236
|
+
def get_agent_card(self, agent_id: str) -> AgentCard:
|
|
237
|
+
return AgentCard(**self._request('GET', f'/a2a/agent-cards/{agent_id}'))
|
|
238
|
+
|
|
239
|
+
# === MCP ===
|
|
240
|
+
def list_tools(self) -> list[McpTool]:
|
|
241
|
+
return [McpTool(**t) for t in self._request('GET', '/mcp/tools')]
|
|
242
|
+
|
|
243
|
+
def create_tool(self, data: McpToolCreate) -> McpTool:
|
|
244
|
+
return McpTool(**self._request('POST', '/mcp/tools', _to_dict(data)))
|
|
245
|
+
|
|
246
|
+
# === ACP ===
|
|
247
|
+
def list_issuers(self) -> list[AcpIssuer]:
|
|
248
|
+
return [AcpIssuer(**i) for i in self._request('GET', '/acp/issuers')]
|
|
249
|
+
|
|
250
|
+
def register_issuer(self, data: AcpIssuerRegister) -> AcpIssuer:
|
|
251
|
+
return AcpIssuer(**self._request('POST', '/acp/issuers', _to_dict(data)))
|
|
252
|
+
|
|
253
|
+
def issue_token(self, data: AcpTokenIssue) -> AcpToken:
|
|
254
|
+
return AcpToken(**self._request('POST', '/acp/tokens', _to_dict(data)))
|
|
255
|
+
|
|
256
|
+
# === ANP ===
|
|
257
|
+
def list_announcements(self) -> list[AnpAnnouncement]:
|
|
258
|
+
return [AnpAnnouncement(**a) for a in self._request('GET', '/anp/announcements')]
|
|
259
|
+
|
|
260
|
+
def announce(self, data: AnpAnnouncementCreate) -> AnpAnnouncement:
|
|
261
|
+
return AnpAnnouncement(**self._request('POST', '/anp/announcements', _to_dict(data)))
|
|
262
|
+
|
|
263
|
+
# === Companies (B2B Multi-Tenant) ===
|
|
264
|
+
def list_companies(self) -> list[Company]:
|
|
265
|
+
return [Company(**c) for c in self._request('GET', '/companies')]
|
|
266
|
+
|
|
267
|
+
def create_company(self, data: CompanyCreate) -> Company:
|
|
268
|
+
return Company(**self._request('POST', '/companies', _to_dict(data)))
|
|
269
|
+
|
|
270
|
+
def get_company(self, company_id: str) -> Company:
|
|
271
|
+
return Company(**self._request('GET', f'/companies/{company_id}'))
|
|
272
|
+
|
|
273
|
+
def list_companies_by_domain(self, domain: str) -> list[Company]:
|
|
274
|
+
return [Company(**c) for c in self._request('GET', f'/companies?domain={domain}')]
|
|
275
|
+
|
|
276
|
+
# === GBP (Google Business Profile) ===
|
|
277
|
+
def list_gbp_accounts(self) -> list[Any]:
|
|
278
|
+
return self._request('GET', '/gbp/accounts')
|
|
279
|
+
|
|
280
|
+
def create_gbp_account(self, data: dict[str, Any]) -> Any:
|
|
281
|
+
return self._request('POST', '/gbp/accounts', data)
|
|
282
|
+
|
|
283
|
+
def sync_gbp_locations(self, account_id: str) -> dict[str, Any]:
|
|
284
|
+
return self._request('POST', f'/gbp/accounts/{account_id}/sync', {})
|
|
285
|
+
|
|
286
|
+
# === Health ===
|
|
287
|
+
def health(self) -> dict[str, Any]:
|
|
288
|
+
return self._request('GET', '/health')
|