agentstr 0.1.11__py3-none-any.whl → 0.1.13__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.
- 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)
|