agentstr 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- agentstr/__init__.py +36 -17
- agentstr/buyer.py +305 -0
- agentstr/buyer.pyi +32 -0
- agentstr/{marketplace.py → merchant.py} +167 -355
- agentstr/merchant.pyi +37 -0
- agentstr/models.py +380 -0
- agentstr/models.pyi +103 -0
- agentstr/nostr.py +412 -79
- agentstr/nostr.pyi +96 -0
- agentstr/py.typed +0 -0
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/METADATA +42 -56
- agentstr-0.1.13.dist-info/RECORD +15 -0
- agentstr-0.1.11.dist-info/RECORD +0 -8
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/LICENSE +0 -0
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/WHEEL +0 -0
- {agentstr-0.1.11.dist-info → agentstr-0.1.13.dist-info}/top_level.txt +0 -0
@@ -1,296 +1,36 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
Module implementing the MerchantTools Toolkit for Agno agents.
|
3
|
+
"""
|
4
|
+
|
2
5
|
import json
|
3
6
|
import logging
|
4
|
-
import
|
5
|
-
from typing import Any,
|
6
|
-
|
7
|
-
from agentstr.nostr import (
|
8
|
-
EventId,
|
9
|
-
Keys,
|
10
|
-
NostrClient,
|
11
|
-
ProductData,
|
12
|
-
ShippingCost,
|
13
|
-
ShippingMethod,
|
14
|
-
StallData,
|
15
|
-
)
|
16
|
-
|
17
|
-
try:
|
18
|
-
from phi.tools import Toolkit
|
19
|
-
except ImportError:
|
20
|
-
raise ImportError(
|
21
|
-
"`phidata` not installed. Please install using `pip install phidata`"
|
22
|
-
)
|
23
|
-
|
24
|
-
from pydantic import BaseModel, ConfigDict, Field, validate_call
|
25
|
-
|
26
|
-
|
27
|
-
class Profile:
|
28
|
-
|
29
|
-
logger = logging.getLogger("Profile")
|
30
|
-
WEB_URL: str = "https://primal.net/p/"
|
31
|
-
|
32
|
-
def __init__(self, name: str, about: str, picture: str, nsec: Optional[str] = None):
|
33
|
-
"""Initialize the profile.
|
34
|
-
|
35
|
-
Args:
|
36
|
-
name: Name for the merchant
|
37
|
-
about: brief description about the merchant
|
38
|
-
picture: url to a png file with a picture for the merchant
|
39
|
-
nsec: optional private key to be used by this Merchant
|
40
|
-
"""
|
41
|
-
|
42
|
-
# Set log handling for MerchantProfile
|
43
|
-
if not Profile.logger.hasHandlers():
|
44
|
-
console_handler = logging.StreamHandler()
|
45
|
-
console_handler.setLevel(logging.INFO)
|
46
|
-
formatter = logging.Formatter(
|
47
|
-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
48
|
-
)
|
49
|
-
console_handler.setFormatter(formatter)
|
50
|
-
Profile.logger.addHandler(console_handler)
|
51
|
-
|
52
|
-
self.name = name
|
53
|
-
self.about = about
|
54
|
-
self.picture = picture
|
55
|
-
|
56
|
-
if nsec:
|
57
|
-
self.private_key = nsec
|
58
|
-
keys = Keys.parse(self.private_key)
|
59
|
-
self.public_key = keys.public_key().to_bech32()
|
60
|
-
Profile.logger.info(
|
61
|
-
f"Pre-defined private key reused for {self.name}: {self.private_key}"
|
62
|
-
)
|
63
|
-
Profile.logger.info(
|
64
|
-
f"Pre-defined public key reused for {self.name}: {self.public_key}"
|
65
|
-
)
|
66
|
-
else:
|
67
|
-
keys = Keys.generate()
|
68
|
-
self.private_key = keys.secret_key().to_bech32()
|
69
|
-
self.public_key = keys.public_key().to_bech32()
|
70
|
-
Profile.logger.info(
|
71
|
-
f"New private key created for {self.name}: {self.private_key}"
|
72
|
-
)
|
73
|
-
Profile.logger.info(
|
74
|
-
f"New public key created for {self.name}: {self.public_key}"
|
75
|
-
)
|
76
|
-
|
77
|
-
self.url = str(self.WEB_URL) + str(self.public_key)
|
78
|
-
|
79
|
-
def __str__(self) -> str:
|
80
|
-
return (
|
81
|
-
"Merchant Profile:\n"
|
82
|
-
"Name = {}\n"
|
83
|
-
"Description = {}\n"
|
84
|
-
"Picture = {}\n"
|
85
|
-
"URL = {}\n"
|
86
|
-
"Private key = {}\n"
|
87
|
-
"Public key = {}".format(
|
88
|
-
self.name,
|
89
|
-
self.about,
|
90
|
-
self.picture,
|
91
|
-
self.url,
|
92
|
-
self.private_key,
|
93
|
-
self.public_key,
|
94
|
-
)
|
95
|
-
)
|
96
|
-
|
97
|
-
def to_dict(self) -> dict:
|
98
|
-
return {
|
99
|
-
"name": self.name,
|
100
|
-
"description": self.about,
|
101
|
-
"picture": self.picture,
|
102
|
-
"public key": self.public_key,
|
103
|
-
"private key": self.private_key,
|
104
|
-
}
|
105
|
-
|
106
|
-
def get_about(self) -> str:
|
107
|
-
"""
|
108
|
-
Returns a description of the Merchant
|
109
|
-
|
110
|
-
Returns:
|
111
|
-
str: description of the Merchant
|
112
|
-
"""
|
113
|
-
return self.about
|
114
|
-
|
115
|
-
def get_name(self) -> str:
|
116
|
-
"""
|
117
|
-
Returns the Merchant's name
|
118
|
-
|
119
|
-
Returns:
|
120
|
-
str: Merchant's name
|
121
|
-
"""
|
122
|
-
return self.name
|
123
|
-
|
124
|
-
def get_picture(self) -> str:
|
125
|
-
"""
|
126
|
-
Returns the picture associated with the Merchant.
|
127
|
-
|
128
|
-
Returns:
|
129
|
-
str: URL to the picture associated with the Merchant
|
130
|
-
"""
|
131
|
-
return self.picture
|
132
|
-
|
133
|
-
def get_private_key(self) -> str:
|
134
|
-
"""
|
135
|
-
Returns the private key.
|
136
|
-
|
137
|
-
Returns:
|
138
|
-
str: private key in bech32 format
|
139
|
-
"""
|
140
|
-
return str(self.private_key)
|
141
|
-
|
142
|
-
def get_public_key(self) -> str:
|
143
|
-
"""
|
144
|
-
Returns the public key.
|
145
|
-
|
146
|
-
Returns:
|
147
|
-
str: public key in bech32 format
|
148
|
-
"""
|
149
|
-
return str(self.public_key)
|
150
|
-
|
151
|
-
def get_url(self) -> str:
|
152
|
-
return str(self.url)
|
153
|
-
|
154
|
-
|
155
|
-
class MerchantProduct(BaseModel):
|
156
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
157
|
-
|
158
|
-
id: str
|
159
|
-
stall_id: str
|
160
|
-
name: str
|
161
|
-
description: str
|
162
|
-
images: List[str]
|
163
|
-
currency: str
|
164
|
-
price: float
|
165
|
-
quantity: int
|
166
|
-
shipping: List[ShippingCost]
|
167
|
-
categories: Optional[List[str]] = []
|
168
|
-
specs: Optional[List[List[str]]] = []
|
169
|
-
|
170
|
-
@classmethod
|
171
|
-
def from_product_data(cls, product: ProductData) -> "MerchantProduct":
|
172
|
-
return cls(
|
173
|
-
id=product.id,
|
174
|
-
stall_id=product.stall_id,
|
175
|
-
name=product.name,
|
176
|
-
description=product.description,
|
177
|
-
images=product.images,
|
178
|
-
currency=product.currency,
|
179
|
-
price=product.price,
|
180
|
-
quantity=product.quantity,
|
181
|
-
shipping=product.shipping,
|
182
|
-
categories=product.categories if product.categories is not None else [],
|
183
|
-
specs=product.specs if product.specs is not None else [],
|
184
|
-
)
|
185
|
-
|
186
|
-
def to_product_data(self) -> ProductData:
|
187
|
-
return ProductData(
|
188
|
-
id=self.id,
|
189
|
-
stall_id=self.stall_id,
|
190
|
-
name=self.name,
|
191
|
-
description=self.description,
|
192
|
-
images=self.images,
|
193
|
-
currency=self.currency,
|
194
|
-
price=self.price,
|
195
|
-
quantity=self.quantity,
|
196
|
-
shipping=self.shipping,
|
197
|
-
categories=self.categories,
|
198
|
-
specs=self.specs,
|
199
|
-
)
|
200
|
-
|
201
|
-
def to_dict(self) -> dict:
|
202
|
-
"""
|
203
|
-
Returns a dictionary representation of the MerchantProduct.
|
204
|
-
ShippingCost class is not serializable, so we need to convert it to a dictionary.
|
205
|
-
|
206
|
-
Returns:
|
207
|
-
dict: dictionary representation of the MerchantProduct
|
208
|
-
"""
|
209
|
-
shipping_dicts = []
|
210
|
-
for shipping in self.shipping:
|
211
|
-
shipping_dicts.append({"id": shipping.id, "cost": shipping.cost})
|
212
|
-
|
213
|
-
return {
|
214
|
-
"id": self.id,
|
215
|
-
"stall_id": self.stall_id,
|
216
|
-
"name": self.name,
|
217
|
-
"description": self.description,
|
218
|
-
"images": self.images,
|
219
|
-
"currency": self.currency,
|
220
|
-
"price": self.price,
|
221
|
-
"quantity": self.quantity,
|
222
|
-
"shipping": shipping_dicts,
|
223
|
-
"categories": self.categories,
|
224
|
-
"specs": self.specs,
|
225
|
-
}
|
226
|
-
|
227
|
-
|
228
|
-
class MerchantStall(BaseModel):
|
229
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
230
|
-
|
231
|
-
id: str
|
232
|
-
name: str
|
233
|
-
description: str
|
234
|
-
currency: str
|
235
|
-
shipping: List[ShippingMethod]
|
236
|
-
|
237
|
-
@classmethod
|
238
|
-
def from_stall_data(cls, stall: StallData) -> "MerchantStall":
|
239
|
-
return cls(
|
240
|
-
id=stall.id(),
|
241
|
-
name=stall.name(),
|
242
|
-
description=stall.description(),
|
243
|
-
currency=stall.currency(),
|
244
|
-
shipping=stall.shipping(),
|
245
|
-
)
|
246
|
-
|
247
|
-
def to_stall_data(self) -> StallData:
|
248
|
-
return StallData(
|
249
|
-
self.id,
|
250
|
-
self.name,
|
251
|
-
self.description,
|
252
|
-
self.currency,
|
253
|
-
self.shipping, # No conversion needed
|
254
|
-
)
|
7
|
+
import time
|
8
|
+
from typing import Any, List, Optional, Tuple, Union
|
255
9
|
|
256
|
-
|
257
|
-
"""
|
258
|
-
Returns a dictionary representation of the MerchantStall.
|
259
|
-
ShippingMethod class is not serializable, so we need to convert it to a dictionary.
|
260
|
-
We can only access cost and id from the ShippingMethod class. We can't access name or regions.
|
10
|
+
from pydantic import ConfigDict
|
261
11
|
|
262
|
-
|
263
|
-
|
264
|
-
"""
|
265
|
-
shipping_dicts = []
|
266
|
-
for shipping in self.shipping:
|
267
|
-
shipping_dicts.append(
|
268
|
-
{
|
269
|
-
"cost": shipping.get_shipping_cost().cost,
|
270
|
-
"id": shipping.get_shipping_cost().id,
|
271
|
-
}
|
272
|
-
)
|
12
|
+
from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
|
13
|
+
from agentstr.nostr import EventId, NostrClient
|
273
14
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
}
|
15
|
+
try:
|
16
|
+
from agno.tools import Toolkit
|
17
|
+
except ImportError as exc:
|
18
|
+
raise ImportError(
|
19
|
+
"`agno` not installed. Please install using `pip install agno`"
|
20
|
+
) from exc
|
281
21
|
|
282
22
|
|
283
|
-
class
|
23
|
+
class MerchantTools(Toolkit):
|
284
24
|
"""
|
285
|
-
|
25
|
+
MerchantTools is a toolkit that allows a merchant to publish
|
26
|
+
products and stalls to Nostr.
|
286
27
|
|
287
28
|
TBD:
|
288
|
-
- Better differentiation between products and stalls in the database
|
29
|
+
- Better differentiation between products and stalls in the database
|
30
|
+
and products and stalls published.
|
289
31
|
|
290
32
|
"""
|
291
33
|
|
292
|
-
from pydantic import ConfigDict
|
293
|
-
|
294
34
|
model_config = ConfigDict(
|
295
35
|
arbitrary_types_allowed=True, extra="allow", validate_assignment=True
|
296
36
|
)
|
@@ -301,7 +41,7 @@ class Merchant(Toolkit):
|
|
301
41
|
|
302
42
|
def __init__(
|
303
43
|
self,
|
304
|
-
merchant_profile:
|
44
|
+
merchant_profile: AgentProfile,
|
305
45
|
relay: str,
|
306
46
|
stalls: List[MerchantStall],
|
307
47
|
products: List[MerchantProduct],
|
@@ -317,9 +57,7 @@ class Merchant(Toolkit):
|
|
317
57
|
super().__init__(name="merchant")
|
318
58
|
self.relay = relay
|
319
59
|
self.merchant_profile = merchant_profile
|
320
|
-
self._nostr_client = NostrClient(
|
321
|
-
self.relay, self.merchant_profile.get_private_key()
|
322
|
-
)
|
60
|
+
self._nostr_client = NostrClient(relay, merchant_profile.get_private_key())
|
323
61
|
|
324
62
|
# initialize the Product DB with no event id
|
325
63
|
self.product_db = [(product, None) for product in products]
|
@@ -327,7 +65,7 @@ class Merchant(Toolkit):
|
|
327
65
|
# initialize the Stall DB with no event id
|
328
66
|
self.stall_db = [(stall, None) for stall in stalls]
|
329
67
|
|
330
|
-
# Register
|
68
|
+
# Register methods
|
331
69
|
self.register(self.get_profile)
|
332
70
|
self.register(self.get_relay)
|
333
71
|
self.register(self.get_products)
|
@@ -345,21 +83,33 @@ class Merchant(Toolkit):
|
|
345
83
|
self.register(self.remove_product_by_name)
|
346
84
|
self.register(self.remove_stall_by_name)
|
347
85
|
|
86
|
+
def get_nostr_client(self) -> NostrClient:
|
87
|
+
"""
|
88
|
+
Get the NostrClient instance
|
89
|
+
"""
|
90
|
+
return self._nostr_client
|
91
|
+
|
348
92
|
def get_profile(self) -> str:
|
349
93
|
"""
|
350
|
-
|
94
|
+
Get the merchant profile in JSON format
|
351
95
|
|
352
96
|
Returns:
|
353
97
|
str: merchant profile in JSON format
|
354
98
|
"""
|
355
|
-
return json.dumps(self.merchant_profile.
|
99
|
+
return json.dumps(self.merchant_profile.to_json())
|
356
100
|
|
357
101
|
def get_relay(self) -> str:
|
102
|
+
"""
|
103
|
+
Get the Nostr relay the merchant is using
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
str: Nostr relay
|
107
|
+
"""
|
358
108
|
return self.relay
|
359
109
|
|
360
110
|
def get_products(self) -> str:
|
361
111
|
"""
|
362
|
-
|
112
|
+
Get all the merchant products
|
363
113
|
|
364
114
|
Returns:
|
365
115
|
str: JSON string containing all products
|
@@ -368,7 +118,7 @@ class Merchant(Toolkit):
|
|
368
118
|
|
369
119
|
def get_stalls(self) -> str:
|
370
120
|
"""
|
371
|
-
|
121
|
+
Get all the merchant stalls in JSON format
|
372
122
|
|
373
123
|
Returns:
|
374
124
|
str: JSON string containing all stalls
|
@@ -379,10 +129,13 @@ class Merchant(Toolkit):
|
|
379
129
|
self,
|
380
130
|
) -> str:
|
381
131
|
"""
|
382
|
-
Publishes or updates
|
132
|
+
Publishes or updates to Nostrall products in the Merchant's Product DB
|
383
133
|
|
384
134
|
Returns:
|
385
135
|
str: JSON array with status of all product publishing operations
|
136
|
+
|
137
|
+
Raises:
|
138
|
+
ValueError: if NostrClient is not initialized
|
386
139
|
"""
|
387
140
|
|
388
141
|
if self._nostr_client is None:
|
@@ -393,9 +146,9 @@ class Merchant(Toolkit):
|
|
393
146
|
for i, (product, _) in enumerate(self.product_db):
|
394
147
|
try:
|
395
148
|
# Convert MerchantProduct to ProductData for nostr_client
|
396
|
-
product_data = product.to_product_data()
|
149
|
+
# product_data = product.to_product_data()
|
397
150
|
# Publish using the SDK's synchronous method
|
398
|
-
event_id = self._nostr_client.publish_product(
|
151
|
+
event_id = self._nostr_client.publish_product(product)
|
399
152
|
self.product_db[i] = (product, event_id)
|
400
153
|
results.append(
|
401
154
|
{
|
@@ -404,8 +157,10 @@ class Merchant(Toolkit):
|
|
404
157
|
"product_name": product.name,
|
405
158
|
}
|
406
159
|
)
|
407
|
-
|
408
|
-
|
160
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
161
|
+
time.sleep(0.5)
|
162
|
+
except RuntimeError as e:
|
163
|
+
logging.error("Unable to publish product %s. Error %s", product, e)
|
409
164
|
results.append(
|
410
165
|
{"status": "error", "message": str(e), "product_name": product.name}
|
411
166
|
)
|
@@ -416,10 +171,14 @@ class Merchant(Toolkit):
|
|
416
171
|
self,
|
417
172
|
) -> str:
|
418
173
|
"""
|
419
|
-
Publishes or updates all stalls managed by the merchant and adds
|
174
|
+
Publishes or updates to Nostr all stalls managed by the merchant and adds
|
175
|
+
the corresponding EventId to the Stall DB
|
420
176
|
|
421
177
|
Returns:
|
422
178
|
str: JSON array with status of all stall publishing operations
|
179
|
+
|
180
|
+
Raises:
|
181
|
+
ValueError: if NostrClient is not initialized
|
423
182
|
"""
|
424
183
|
if self._nostr_client is None:
|
425
184
|
raise ValueError("NostrClient not initialized")
|
@@ -427,9 +186,9 @@ class Merchant(Toolkit):
|
|
427
186
|
|
428
187
|
for i, (stall, _) in enumerate(self.stall_db):
|
429
188
|
try:
|
430
|
-
#
|
431
|
-
stall_data = stall.to_stall_data()
|
432
|
-
event_id = self._nostr_client.publish_stall(
|
189
|
+
# We don't need to convert MerchantStall to StallData for nostr_client
|
190
|
+
# stall_data = stall.to_stall_data()
|
191
|
+
event_id = self._nostr_client.publish_stall(stall)
|
433
192
|
self.stall_db[i] = (stall, event_id)
|
434
193
|
results.append(
|
435
194
|
{
|
@@ -438,8 +197,10 @@ class Merchant(Toolkit):
|
|
438
197
|
"stall_name": stall.name,
|
439
198
|
}
|
440
199
|
)
|
441
|
-
|
442
|
-
|
200
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
201
|
+
time.sleep(0.5)
|
202
|
+
except RuntimeError as e:
|
203
|
+
logging.error("Unable to publish stall %s. Error %s", stall, e)
|
443
204
|
results.append(
|
444
205
|
{"status": "error", "message": str(e), "stall_name": stall.name}
|
445
206
|
)
|
@@ -448,13 +209,17 @@ class Merchant(Toolkit):
|
|
448
209
|
|
449
210
|
def publish_new_product(self, product: MerchantProduct) -> str:
|
450
211
|
"""
|
451
|
-
Publishes
|
212
|
+
Publishes to Nostra new product that is not currently in the Merchant's
|
213
|
+
Product DB and adds it to the Product DB
|
452
214
|
|
453
215
|
Args:
|
454
216
|
product: MerchantProduct to be published
|
455
217
|
|
456
218
|
Returns:
|
457
219
|
str: JSON string with status of the operation
|
220
|
+
|
221
|
+
Raises:
|
222
|
+
ValueError: if NostrClient is not initialized
|
458
223
|
"""
|
459
224
|
if self._nostr_client is None:
|
460
225
|
raise ValueError("NostrClient not initialized")
|
@@ -473,19 +238,23 @@ class Merchant(Toolkit):
|
|
473
238
|
"product_name": product.name,
|
474
239
|
}
|
475
240
|
)
|
476
|
-
except
|
241
|
+
except RuntimeError as e:
|
477
242
|
return json.dumps(
|
478
243
|
{"status": "error", "message": str(e), "product_name": product.name}
|
479
244
|
)
|
480
245
|
|
481
246
|
def publish_product_by_name(self, arguments: str) -> str:
|
482
247
|
"""
|
483
|
-
Publishes or updates
|
248
|
+
Publishes or updates to Nostra given product from the Merchant's Product DB
|
484
249
|
Args:
|
485
|
-
arguments: JSON string that may contain
|
250
|
+
arguments: JSON string that may contain
|
251
|
+
{"name": "product_name"} or just "product_name"
|
486
252
|
|
487
253
|
Returns:
|
488
254
|
str: JSON string with status of the operation
|
255
|
+
|
256
|
+
Raises:
|
257
|
+
ValueError: if NostrClient is not initialized
|
489
258
|
"""
|
490
259
|
if self._nostr_client is None:
|
491
260
|
raise ValueError("NostrClient not initialized")
|
@@ -508,11 +277,13 @@ class Merchant(Toolkit):
|
|
508
277
|
if product.name == name:
|
509
278
|
try:
|
510
279
|
# Convert MerchantProduct to ProductData for nostr_client
|
511
|
-
product_data = product.to_product_data()
|
280
|
+
# product_data = product.to_product_data()
|
512
281
|
# Publish using the SDK's synchronous method
|
513
|
-
event_id = self._nostr_client.publish_product(
|
282
|
+
event_id = self._nostr_client.publish_product(product)
|
514
283
|
# Update the product_db with the new event_id
|
515
284
|
self.product_db[i] = (product, event_id)
|
285
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
286
|
+
time.sleep(0.5)
|
516
287
|
return json.dumps(
|
517
288
|
{
|
518
289
|
"status": "success",
|
@@ -520,7 +291,7 @@ class Merchant(Toolkit):
|
|
520
291
|
"product_name": product.name,
|
521
292
|
}
|
522
293
|
)
|
523
|
-
except
|
294
|
+
except RuntimeError as e:
|
524
295
|
return json.dumps(
|
525
296
|
{
|
526
297
|
"status": "error",
|
@@ -540,7 +311,7 @@ class Merchant(Toolkit):
|
|
540
311
|
|
541
312
|
def publish_products_by_stall_name(self, arguments: Union[str, dict]) -> str:
|
542
313
|
"""
|
543
|
-
Publishes or updates all products sold by the merchant in a given stall
|
314
|
+
Publishes or updates to Nostr all products sold by the merchant in a given stall
|
544
315
|
|
545
316
|
Args:
|
546
317
|
arguments: str or dict with the stall name. Can be in formats:
|
@@ -550,6 +321,9 @@ class Merchant(Toolkit):
|
|
550
321
|
|
551
322
|
Returns:
|
552
323
|
str: JSON array with status of all product publishing operations
|
324
|
+
|
325
|
+
Raises:
|
326
|
+
ValueError: if NostrClient is not initialized
|
553
327
|
"""
|
554
328
|
if self._nostr_client is None:
|
555
329
|
raise ValueError("NostrClient not initialized")
|
@@ -604,8 +378,8 @@ class Merchant(Toolkit):
|
|
604
378
|
for i, (product, _) in enumerate(self.product_db):
|
605
379
|
if product.stall_id == stall_id:
|
606
380
|
try:
|
607
|
-
product_data = product.to_product_data()
|
608
|
-
event_id = self._nostr_client.publish_product(
|
381
|
+
# product_data = product.to_product_data()
|
382
|
+
event_id = self._nostr_client.publish_product(product)
|
609
383
|
self.product_db[i] = (product, event_id)
|
610
384
|
results.append(
|
611
385
|
{
|
@@ -615,7 +389,9 @@ class Merchant(Toolkit):
|
|
615
389
|
"stall_name": stall_name,
|
616
390
|
}
|
617
391
|
)
|
618
|
-
|
392
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
393
|
+
time.sleep(0.5)
|
394
|
+
except RuntimeError as e:
|
619
395
|
results.append(
|
620
396
|
{
|
621
397
|
"status": "error",
|
@@ -638,14 +414,14 @@ class Merchant(Toolkit):
|
|
638
414
|
|
639
415
|
return json.dumps(results)
|
640
416
|
|
641
|
-
except
|
417
|
+
except RuntimeError as e:
|
642
418
|
return json.dumps(
|
643
419
|
[{"status": "error", "message": str(e), "arguments": str(arguments)}]
|
644
420
|
)
|
645
421
|
|
646
422
|
def publish_profile(self) -> str:
|
647
423
|
"""
|
648
|
-
Publishes the profile
|
424
|
+
Publishes the profile to Nostr
|
649
425
|
|
650
426
|
Returns:
|
651
427
|
str: JSON of the event that published the profile
|
@@ -663,27 +439,32 @@ class Merchant(Toolkit):
|
|
663
439
|
self.merchant_profile.get_picture(),
|
664
440
|
)
|
665
441
|
return json.dumps(event_id.__dict__)
|
666
|
-
except
|
667
|
-
raise RuntimeError(f"Unable to publish the profile: {e}")
|
442
|
+
except RuntimeError as e:
|
443
|
+
raise RuntimeError(f"Unable to publish the profile: {e}") from e
|
668
444
|
|
669
445
|
def publish_new_stall(self, stall: MerchantStall) -> str:
|
670
446
|
"""
|
671
|
-
Publishes a new stall that is not currently in the Merchant's
|
447
|
+
Publishes to Nostr a new stall that is not currently in the Merchant's
|
448
|
+
Stall DB and adds it to the Stall DB
|
672
449
|
|
673
450
|
Args:
|
674
451
|
stall: MerchantStall to be published
|
675
452
|
|
676
453
|
Returns:
|
677
454
|
str: JSON string with status of the operation
|
455
|
+
|
456
|
+
Raises:
|
457
|
+
ValueError: if NostrClient is not initialized
|
678
458
|
"""
|
679
459
|
if self._nostr_client is None:
|
680
460
|
raise ValueError("NostrClient not initialized")
|
681
461
|
|
682
462
|
try:
|
683
|
-
#
|
684
|
-
|
685
|
-
#
|
686
|
-
|
463
|
+
# We don't ned to convert to StallData.
|
464
|
+
# nostr_client.publish_stall() accepts a MerchantStall
|
465
|
+
# stall_data = stall.to_stall_data()
|
466
|
+
# Publish using the synchronous method
|
467
|
+
event_id = self._nostr_client.publish_stall(stall)
|
687
468
|
# we need to add the stall event id to the stall db
|
688
469
|
self.stall_db.append((stall, event_id))
|
689
470
|
return json.dumps(
|
@@ -693,12 +474,27 @@ class Merchant(Toolkit):
|
|
693
474
|
"stall_name": stall.name,
|
694
475
|
}
|
695
476
|
)
|
696
|
-
except
|
477
|
+
except RuntimeError as e:
|
697
478
|
return json.dumps(
|
698
479
|
{"status": "error", "message": str(e), "stall_name": stall.name}
|
699
480
|
)
|
700
481
|
|
701
482
|
def publish_stall_by_name(self, arguments: Union[str, dict]) -> str:
|
483
|
+
"""
|
484
|
+
Publishes or updates to Nostr a given stall by name
|
485
|
+
|
486
|
+
Args:
|
487
|
+
arguments: str or dict with the stall name. Can be in formats:
|
488
|
+
- {"name": "stall_name"}
|
489
|
+
- {"arguments": "{\"name\": \"stall_name\"}"}
|
490
|
+
- "stall_name"
|
491
|
+
|
492
|
+
Returns:
|
493
|
+
str: JSON array with status of the operation
|
494
|
+
|
495
|
+
Raises:
|
496
|
+
ValueError: if NostrClient is not initialized
|
497
|
+
"""
|
702
498
|
if self._nostr_client is None:
|
703
499
|
raise ValueError("NostrClient not initialized")
|
704
500
|
|
@@ -735,9 +531,10 @@ class Merchant(Toolkit):
|
|
735
531
|
for i, (stall, _) in enumerate(self.stall_db):
|
736
532
|
if stall.name == stall_name:
|
737
533
|
try:
|
738
|
-
|
739
|
-
event_id = self._nostr_client.publish_stall(stall_data)
|
534
|
+
event_id = self._nostr_client.publish_stall(stall)
|
740
535
|
self.stall_db[i] = (stall, event_id)
|
536
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
537
|
+
time.sleep(0.5)
|
741
538
|
return json.dumps(
|
742
539
|
{
|
743
540
|
"status": "success",
|
@@ -745,7 +542,8 @@ class Merchant(Toolkit):
|
|
745
542
|
"stall_name": stall.name,
|
746
543
|
}
|
747
544
|
)
|
748
|
-
|
545
|
+
|
546
|
+
except RuntimeError as e:
|
749
547
|
return json.dumps(
|
750
548
|
[
|
751
549
|
{
|
@@ -767,17 +565,20 @@ class Merchant(Toolkit):
|
|
767
565
|
]
|
768
566
|
)
|
769
567
|
|
770
|
-
except
|
568
|
+
except RuntimeError as e:
|
771
569
|
return json.dumps(
|
772
570
|
[{"status": "error", "message": str(e), "stall_name": "unknown"}]
|
773
571
|
)
|
774
572
|
|
775
573
|
def remove_all_products(self) -> str:
|
776
574
|
"""
|
777
|
-
Removes all published
|
575
|
+
Removes from Nostr all products published by the merchant
|
778
576
|
|
779
577
|
Returns:
|
780
578
|
str: JSON array with status of all product removal operations
|
579
|
+
|
580
|
+
Raises:
|
581
|
+
ValueError: if NostrClient is not initialized
|
781
582
|
"""
|
782
583
|
if self._nostr_client is None:
|
783
584
|
raise ValueError("NostrClient not initialized")
|
@@ -789,7 +590,9 @@ class Merchant(Toolkit):
|
|
789
590
|
results.append(
|
790
591
|
{
|
791
592
|
"status": "skipped",
|
792
|
-
"message":
|
593
|
+
"message": (
|
594
|
+
f"Product '{product.name}' has not been published yet"
|
595
|
+
),
|
793
596
|
"product_name": product.name,
|
794
597
|
}
|
795
598
|
)
|
@@ -810,7 +613,9 @@ class Merchant(Toolkit):
|
|
810
613
|
"event_id": str(event_id),
|
811
614
|
}
|
812
615
|
)
|
813
|
-
|
616
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
617
|
+
time.sleep(0.5)
|
618
|
+
except RuntimeError as e:
|
814
619
|
results.append(
|
815
620
|
{"status": "error", "message": str(e), "product_name": product.name}
|
816
621
|
)
|
@@ -819,10 +624,14 @@ class Merchant(Toolkit):
|
|
819
624
|
|
820
625
|
def remove_all_stalls(self) -> str:
|
821
626
|
"""
|
822
|
-
Removes all stalls
|
627
|
+
Removes from Nostr all stalls from the merchant and their
|
628
|
+
corresponding products
|
823
629
|
|
824
630
|
Returns:
|
825
631
|
str: JSON array with status of all removal operations
|
632
|
+
|
633
|
+
Raises:
|
634
|
+
ValueError: if NostrClient is not initialized
|
826
635
|
"""
|
827
636
|
if self._nostr_client is None:
|
828
637
|
raise ValueError("NostrClient not initialized")
|
@@ -841,7 +650,7 @@ class Merchant(Toolkit):
|
|
841
650
|
results.append(
|
842
651
|
{
|
843
652
|
"status": "skipped",
|
844
|
-
"message":
|
653
|
+
"message": "Unpublished product",
|
845
654
|
"product_name": product.name,
|
846
655
|
"stall_name": stall_name,
|
847
656
|
}
|
@@ -863,7 +672,7 @@ class Merchant(Toolkit):
|
|
863
672
|
"event_id": str(event_id),
|
864
673
|
}
|
865
674
|
)
|
866
|
-
except
|
675
|
+
except RuntimeError as e:
|
867
676
|
results.append(
|
868
677
|
{
|
869
678
|
"status": "error",
|
@@ -879,7 +688,7 @@ class Merchant(Toolkit):
|
|
879
688
|
results.append(
|
880
689
|
{
|
881
690
|
"status": "skipped",
|
882
|
-
"message": f"Stall '{stall_name}' has not been published yet",
|
691
|
+
"message": (f"Stall '{stall_name}' has not been published yet"),
|
883
692
|
"stall_name": stall_name,
|
884
693
|
}
|
885
694
|
)
|
@@ -897,7 +706,9 @@ class Merchant(Toolkit):
|
|
897
706
|
"event_id": str(stall_event_id),
|
898
707
|
}
|
899
708
|
)
|
900
|
-
|
709
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
710
|
+
time.sleep(0.5)
|
711
|
+
except RuntimeError as e:
|
901
712
|
results.append(
|
902
713
|
{"status": "error", "message": str(e), "stall_name": stall_name}
|
903
714
|
)
|
@@ -906,13 +717,17 @@ class Merchant(Toolkit):
|
|
906
717
|
|
907
718
|
def remove_product_by_name(self, arguments: str) -> str:
|
908
719
|
"""
|
909
|
-
|
720
|
+
Removes from Nostr a product with the given name
|
910
721
|
|
911
722
|
Args:
|
912
|
-
arguments: JSON string that may contain {"name": "product_name"}
|
723
|
+
arguments: JSON string that may contain {"name": "product_name"}
|
724
|
+
or just "product_name"
|
913
725
|
|
914
726
|
Returns:
|
915
727
|
str: JSON string with status of the operation
|
728
|
+
|
729
|
+
Raises:
|
730
|
+
ValueError: if NostrClient is not initialized
|
916
731
|
"""
|
917
732
|
if self._nostr_client is None:
|
918
733
|
raise ValueError("NostrClient not initialized")
|
@@ -949,6 +764,8 @@ class Merchant(Toolkit):
|
|
949
764
|
)
|
950
765
|
# Remove the event_id, keeping the product in the database
|
951
766
|
self.product_db[i] = (product, None)
|
767
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
768
|
+
time.sleep(0.5)
|
952
769
|
return json.dumps(
|
953
770
|
{
|
954
771
|
"status": "success",
|
@@ -957,7 +774,7 @@ class Merchant(Toolkit):
|
|
957
774
|
"event_id": str(event_id),
|
958
775
|
}
|
959
776
|
)
|
960
|
-
except
|
777
|
+
except RuntimeError as e:
|
961
778
|
return json.dumps(
|
962
779
|
{"status": "error", "message": str(e), "product_name": name}
|
963
780
|
)
|
@@ -972,7 +789,8 @@ class Merchant(Toolkit):
|
|
972
789
|
)
|
973
790
|
|
974
791
|
def remove_stall_by_name(self, arguments: Union[str, dict]) -> str:
|
975
|
-
"""
|
792
|
+
"""
|
793
|
+
Remove from Nostr a stall and its products by name
|
976
794
|
|
977
795
|
Args:
|
978
796
|
arguments: str or dict with the stall name. Can be in formats:
|
@@ -982,6 +800,9 @@ class Merchant(Toolkit):
|
|
982
800
|
|
983
801
|
Returns:
|
984
802
|
str: JSON array with status of the operation
|
803
|
+
|
804
|
+
Raises:
|
805
|
+
ValueError: if NostrClient is not initialized
|
985
806
|
"""
|
986
807
|
if self._nostr_client is None:
|
987
808
|
raise ValueError("NostrClient not initialized")
|
@@ -1042,7 +863,7 @@ class Merchant(Toolkit):
|
|
1042
863
|
results.append(
|
1043
864
|
{
|
1044
865
|
"status": "skipped",
|
1045
|
-
"message":
|
866
|
+
"message": "Unpublished product",
|
1046
867
|
"product_name": product.name,
|
1047
868
|
"stall_name": stall_name,
|
1048
869
|
}
|
@@ -1063,7 +884,9 @@ class Merchant(Toolkit):
|
|
1063
884
|
"event_id": str(event_id),
|
1064
885
|
}
|
1065
886
|
)
|
1066
|
-
|
887
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
888
|
+
time.sleep(0.5)
|
889
|
+
except RuntimeError as e:
|
1067
890
|
results.append(
|
1068
891
|
{
|
1069
892
|
"status": "error",
|
@@ -1080,7 +903,9 @@ class Merchant(Toolkit):
|
|
1080
903
|
results.append(
|
1081
904
|
{
|
1082
905
|
"status": "skipped",
|
1083
|
-
"message":
|
906
|
+
"message": (
|
907
|
+
f"Stall '{stall_name}' has not been published yet"
|
908
|
+
),
|
1084
909
|
"stall_name": stall_name,
|
1085
910
|
}
|
1086
911
|
)
|
@@ -1101,7 +926,7 @@ class Merchant(Toolkit):
|
|
1101
926
|
"event_id": str(stall_event_id),
|
1102
927
|
}
|
1103
928
|
)
|
1104
|
-
except
|
929
|
+
except RuntimeError as e:
|
1105
930
|
results.append(
|
1106
931
|
{
|
1107
932
|
"status": "error",
|
@@ -1112,20 +937,7 @@ class Merchant(Toolkit):
|
|
1112
937
|
|
1113
938
|
return json.dumps(results)
|
1114
939
|
|
1115
|
-
except
|
940
|
+
except RuntimeError as e:
|
1116
941
|
return json.dumps(
|
1117
942
|
[{"status": "error", "message": str(e), "stall_name": "unknown"}]
|
1118
943
|
)
|
1119
|
-
|
1120
|
-
def get_event_id(self, response: Any) -> str:
|
1121
|
-
"""Convert any response to a string event ID.
|
1122
|
-
|
1123
|
-
Args:
|
1124
|
-
response: Response that might contain an event ID
|
1125
|
-
|
1126
|
-
Returns:
|
1127
|
-
str: String representation of event ID or empty string if None
|
1128
|
-
"""
|
1129
|
-
if response is None:
|
1130
|
-
return ""
|
1131
|
-
return str(response)
|