agentstr 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
agentstr/merchant.pyi ADDED
@@ -0,0 +1,37 @@
1
+ from typing import Any, List
2
+
3
+ from agno.tools import Toolkit
4
+ from nostr_sdk import NostrClient # type: ignore
5
+
6
+ from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
7
+
8
+ class MerchantTools(Toolkit):
9
+ _nostr_client: NostrClient
10
+ _profile: AgentProfile
11
+ _stalls: List[MerchantStall]
12
+ _products: List[MerchantProduct]
13
+
14
+ def __init__(
15
+ self,
16
+ merchant_profile: AgentProfile,
17
+ relay: str,
18
+ stalls: List[MerchantStall],
19
+ products: List[MerchantProduct],
20
+ ) -> None: ...
21
+ def get_profile(self) -> str: ...
22
+ def get_relay(self) -> str: ...
23
+ def get_products(self) -> str: ...
24
+ def get_stalls(self) -> str: ...
25
+ def publish_all_products(self) -> str: ...
26
+ def publish_all_stalls(self) -> str: ...
27
+ def publish_new_product(self, product: MerchantProduct) -> str: ...
28
+ def publish_product_by_name(self, product_name: str) -> str: ...
29
+ def publish_products_by_stall_name(self, stall_name: str) -> str: ...
30
+ def publish_profile(self) -> str: ...
31
+ def publish_new_stall(self, stall: MerchantStall) -> str: ...
32
+ def publish_stall_by_name(self, stall_name: str) -> str: ...
33
+ def remove_all_products(self) -> str: ...
34
+ def remove_all_stalls(self) -> str: ...
35
+ def remove_product_by_name(self, product_name: str) -> str: ...
36
+ def remove_stall_by_name(self, stall_name: str) -> str: ...
37
+ def get_event_id(self, response: Any) -> str: ...
agentstr/models.py ADDED
@@ -0,0 +1,381 @@
1
+ import json
2
+ import logging
3
+ from typing import List, Optional
4
+
5
+ from nostr_sdk import (
6
+ Keys,
7
+ Metadata,
8
+ ProductData,
9
+ PublicKey,
10
+ ShippingCost,
11
+ ShippingMethod,
12
+ StallData,
13
+ )
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+ __all__ = [
17
+ "Profile",
18
+ "NostrProfile",
19
+ "AgentProfile",
20
+ "MerchantProduct",
21
+ "MerchantStall",
22
+ ]
23
+
24
+
25
+ class Profile:
26
+ """
27
+ Generic Profile class that holds the metadata of a Nostr profile.
28
+ """
29
+
30
+ logger = logging.getLogger("Profile")
31
+
32
+ def __init__(self) -> None:
33
+ self.about = ""
34
+ self.display_name = ""
35
+ self.name = ""
36
+ self.picture = ""
37
+ self.website = ""
38
+
39
+ def get_about(self) -> str:
40
+ return self.about
41
+
42
+ def get_display_name(self) -> str:
43
+ return self.display_name
44
+
45
+ def get_name(self) -> str:
46
+ return self.name
47
+
48
+ def get_picture(self) -> str:
49
+ return self.picture
50
+
51
+ def get_website(self) -> str:
52
+ return self.website
53
+
54
+ def set_about(self, about: str) -> None:
55
+ self.about = about
56
+
57
+ def set_display_name(self, display_name: str) -> None:
58
+ self.display_name = display_name
59
+
60
+ def set_name(self, name: str) -> None:
61
+ self.name = name
62
+
63
+ def set_picture(self, picture: str) -> None:
64
+ self.picture = picture
65
+
66
+ def set_website(self, website: str) -> None:
67
+ self.website = website
68
+
69
+ def to_json(self) -> str:
70
+ data = {
71
+ "name": self.name,
72
+ "display_name": self.display_name,
73
+ "about": self.about,
74
+ "picture": self.picture,
75
+ "website": self.website,
76
+ }
77
+ return json.dumps(data)
78
+
79
+
80
+ class NostrProfile(Profile):
81
+ """
82
+ NostrProfile is a Profile that is used to represent a public Nostr profile.
83
+
84
+ Key difference between NostrProfile and AgentProfile is that NostrProfile represents
85
+ a third party profile and therefore it only has a public key.
86
+ """
87
+
88
+ WEB_URL: str = "https://primal.net/p/"
89
+
90
+ def __init__(self, public_key: PublicKey) -> None:
91
+ super().__init__()
92
+ self.public_key = public_key
93
+ self.profile_url = self.WEB_URL + self.public_key.to_bech32()
94
+ # Initialize the locations set here, per-instance
95
+ self.locations: set[str] = set()
96
+
97
+ @classmethod
98
+ def from_metadata(cls, metadata: Metadata, public_key: PublicKey) -> "NostrProfile":
99
+ profile = cls(public_key)
100
+ profile.set_about(metadata.get_about())
101
+ profile.set_display_name(metadata.get_display_name())
102
+ profile.set_name(metadata.get_name())
103
+ profile.set_picture(metadata.get_picture())
104
+ profile.set_website(metadata.get_website())
105
+ return profile
106
+
107
+ def add_location(self, location: str) -> None:
108
+ """Add a location to the Nostr profile.
109
+
110
+ Args:
111
+ location: location to add
112
+ """
113
+ self.locations.add(location)
114
+
115
+ def get_public_key(self) -> str:
116
+ """Get the public key of the Nostr profile.
117
+
118
+ Returns:
119
+ str: bech32 encoded public key of the Nostr profile
120
+ """
121
+ return str(self.public_key.to_bech32())
122
+
123
+ def get_locations(self) -> set[str]:
124
+ """Get the locations of the Nostr profile.
125
+
126
+ Returns:
127
+ set[str]: set with locations of the Nostr profile
128
+ """
129
+ return self.locations
130
+
131
+ def get_profile_url(self) -> str:
132
+ return self.profile_url
133
+
134
+ def get_zip_codes(self) -> List[str]:
135
+ return self.zip_codes
136
+
137
+ def to_json(self) -> str:
138
+ # Ensure super().to_json() returns a dictionary
139
+ parent_json = super().to_json()
140
+ data = json.loads(parent_json) if isinstance(parent_json, str) else parent_json
141
+
142
+ # Add NostrProfile-specific fields
143
+ data.update(
144
+ {
145
+ "profile_url": self.profile_url,
146
+ "public_key": self.public_key.to_bech32(),
147
+ "locations": (
148
+ list(self.locations) if self.locations else []
149
+ ), # Convert set to list
150
+ }
151
+ )
152
+
153
+ return json.dumps(data)
154
+
155
+ def __eq__(self, other: object) -> bool:
156
+ if not isinstance(other, NostrProfile):
157
+ return False
158
+ return str(self.public_key.to_bech32()) == str(other.public_key.to_bech32())
159
+
160
+ def __hash__(self) -> int:
161
+ return hash(str(self.public_key.to_bech32()))
162
+
163
+ def to_dict(self) -> dict:
164
+ """
165
+ Returns a dictionary representation of the NostrProfile.
166
+
167
+ Returns:
168
+ dict: dictionary representation of the NostrProfile
169
+ """
170
+ return {
171
+ "profile_url": self.profile_url,
172
+ "public_key": self.public_key.to_bech32(),
173
+ "locations": list(self.locations), # Convert set to list
174
+ "name": self.name,
175
+ "display_name": self.display_name,
176
+ "about": self.about,
177
+ "picture": self.picture,
178
+ "website": self.website,
179
+ }
180
+
181
+
182
+ class AgentProfile(Profile):
183
+ """
184
+ AgentProfile is a Profile that is used to represent an agent.
185
+ """
186
+
187
+ WEB_URL: str = "https://primal.net/p/"
188
+
189
+ def __init__(self, keys: Keys) -> None:
190
+ super().__init__()
191
+ self.keys = keys
192
+ self.profile_url = self.WEB_URL + self.keys.public_key().to_bech32()
193
+
194
+ @classmethod
195
+ def from_metadata(cls, metadata: Metadata, keys: Keys) -> "AgentProfile":
196
+ profile = cls(keys)
197
+ profile.set_about(metadata.get_about())
198
+ profile.set_display_name(metadata.get_display_name())
199
+ profile.set_name(metadata.get_name())
200
+ profile.set_picture(metadata.get_picture())
201
+ profile.set_website(metadata.get_website())
202
+ return profile
203
+
204
+ def get_private_key(self) -> str:
205
+ return str(self.keys.secret_key().to_bech32())
206
+
207
+ def get_public_key(self) -> str:
208
+ return str(self.keys.public_key().to_bech32())
209
+
210
+ def to_json(self) -> str:
211
+ # Parse parent's JSON string back to dict
212
+ data = json.loads(super().to_json())
213
+ # Add AgentProfile-specific fields
214
+ data.update(
215
+ {
216
+ "profile_url": self.profile_url,
217
+ "public_key": self.keys.public_key().to_bech32(),
218
+ "private_key": self.keys.secret_key().to_bech32(),
219
+ }
220
+ )
221
+ return json.dumps(data)
222
+
223
+
224
+ class MerchantProduct(BaseModel):
225
+ model_config = ConfigDict(arbitrary_types_allowed=True)
226
+
227
+ id: str
228
+ stall_id: str
229
+ name: str
230
+ description: str
231
+ images: List[str]
232
+ currency: str
233
+ price: float
234
+ quantity: int
235
+ shipping: List[ShippingCost]
236
+ categories: List[str] = Field(default_factory=list)
237
+ specs: List[List[str]] = Field(default_factory=list)
238
+
239
+ @classmethod
240
+ def from_product_data(cls, product_data: "ProductData") -> "MerchantProduct":
241
+ # print(f"Raw product data specs: {product_data.specs}") # Debug print
242
+ shipping_costs = []
243
+ for ship in product_data.shipping:
244
+ if isinstance(ship, dict):
245
+ shipping_costs.append(ShippingCost(id=ship["id"], cost=ship["cost"]))
246
+ else:
247
+ shipping_costs.append(ship)
248
+
249
+ # Handle specs - ensure it's a list
250
+ specs = []
251
+ if product_data.specs is not None:
252
+ if isinstance(product_data.specs, dict):
253
+ # Convert dict to list of lists if needed
254
+ specs = [[k, v] for k, v in product_data.specs.items()]
255
+ elif isinstance(product_data.specs, list):
256
+ specs = product_data.specs
257
+
258
+ return cls(
259
+ id=product_data.id,
260
+ stall_id=product_data.stall_id,
261
+ name=product_data.name,
262
+ description=product_data.description,
263
+ images=product_data.images,
264
+ currency=product_data.currency,
265
+ price=product_data.price,
266
+ quantity=product_data.quantity,
267
+ shipping=shipping_costs,
268
+ categories=(
269
+ product_data.categories if product_data.categories is not None else []
270
+ ),
271
+ specs=specs,
272
+ )
273
+
274
+ def to_product_data(self) -> "ProductData":
275
+ try:
276
+ return ProductData(
277
+ id=self.id,
278
+ stall_id=self.stall_id,
279
+ name=self.name,
280
+ description=self.description,
281
+ images=self.images,
282
+ currency=self.currency,
283
+ price=self.price,
284
+ quantity=self.quantity,
285
+ shipping=self.shipping,
286
+ categories=self.categories,
287
+ specs=self.specs,
288
+ )
289
+ except Exception as e:
290
+ logging.error(f"Failed to convert to ProductData: {e}")
291
+ logging.error(f"Shipping data: {self.shipping}")
292
+ raise
293
+
294
+ def to_dict(self) -> dict:
295
+ """
296
+ Returns a dictionary representation of the MerchantProduct.
297
+ ShippingCost class is not serializable, so we need to convert it to a dictionary.
298
+
299
+ Returns:
300
+ dict: dictionary representation of the MerchantProduct
301
+ """
302
+ shipping_dicts = []
303
+ for shipping in self.shipping:
304
+ shipping_dicts.append({"id": shipping.id, "cost": shipping.cost})
305
+
306
+ return {
307
+ "id": self.id,
308
+ "stall_id": self.stall_id,
309
+ "name": self.name,
310
+ "description": self.description,
311
+ "images": self.images,
312
+ "currency": self.currency,
313
+ "price": self.price,
314
+ "quantity": self.quantity,
315
+ "shipping": shipping_dicts,
316
+ "categories": self.categories,
317
+ "specs": self.specs,
318
+ }
319
+
320
+
321
+ class MerchantStall(BaseModel):
322
+ model_config = ConfigDict(arbitrary_types_allowed=True)
323
+
324
+ id: str
325
+ name: str
326
+ description: str
327
+ currency: str
328
+ shipping: List[ShippingMethod]
329
+ geohash: str
330
+
331
+ @classmethod
332
+ def from_stall_data(cls, stall: "StallData") -> "MerchantStall":
333
+ return cls(
334
+ id=stall.id(),
335
+ name=stall.name(),
336
+ description=stall.description(),
337
+ currency=stall.currency(),
338
+ shipping=stall.shipping(),
339
+ )
340
+
341
+ def get_geohash(self) -> str:
342
+ return self.geohash
343
+
344
+ def set_geohash(self, geohash: str) -> None:
345
+ self.geohash = geohash
346
+
347
+ def to_dict(self) -> dict:
348
+ """
349
+ Returns a dictionary representation of the MerchantStall.
350
+ ShippingMethod class is not serializable, so we need to convert it to a dictionary.
351
+ We can only access cost and id from the ShippingMethod class. We can't access name or regions.
352
+
353
+ Returns:
354
+ dict: dictionary representation of the MerchantStall
355
+ """
356
+ shipping_dicts = []
357
+ for shipping in self.shipping:
358
+ shipping_dicts.append(
359
+ {
360
+ "cost": shipping.get_shipping_cost().cost,
361
+ "id": shipping.get_shipping_cost().id,
362
+ }
363
+ )
364
+
365
+ return {
366
+ "id": self.id,
367
+ "name": self.name,
368
+ "description": self.description,
369
+ "currency": self.currency,
370
+ "shipping zones": [shipping_dicts],
371
+ "geohash": self.geohash,
372
+ }
373
+
374
+ def to_stall_data(self) -> "StallData":
375
+ return StallData(
376
+ self.id,
377
+ self.name,
378
+ self.description,
379
+ self.currency,
380
+ self.shipping, # No conversion needed
381
+ )
agentstr/models.pyi ADDED
@@ -0,0 +1,103 @@
1
+ from logging import Logger
2
+ from typing import ClassVar, List, Optional, Set
3
+
4
+ from nostr_sdk import (
5
+ Keys,
6
+ Metadata,
7
+ ProductData,
8
+ PublicKey,
9
+ ShippingCost,
10
+ ShippingMethod,
11
+ StallData,
12
+ )
13
+ from pydantic import BaseModel
14
+
15
+ class Profile:
16
+ """
17
+ Generic Profile class that holds the metadata of a Nostr profile.
18
+ """
19
+
20
+ logger: ClassVar[Logger]
21
+ about: str
22
+ display_name: str
23
+ name: str
24
+ picture: str
25
+ website: str
26
+
27
+ def __init__(self) -> None: ...
28
+ def get_about(self) -> str: ...
29
+ def get_display_name(self) -> str: ...
30
+ def get_name(self) -> str: ...
31
+ def get_picture(self) -> str: ...
32
+ def get_website(self) -> str: ...
33
+ def set_about(self, about: str) -> None: ...
34
+ def set_display_name(self, display_name: str) -> None: ...
35
+ def set_name(self, name: str) -> None: ...
36
+ def set_picture(self, picture: str) -> None: ...
37
+ def set_website(self, website: str) -> None: ...
38
+ def to_json(self) -> str: ...
39
+
40
+ class AgentProfile(Profile):
41
+ WEB_URL: ClassVar[str]
42
+ profile_url: str
43
+ keys: Keys
44
+
45
+ def __init__(self, keys: Keys) -> None: ...
46
+ @classmethod
47
+ def from_metadata(cls, metadata: Metadata, keys: Keys) -> "AgentProfile": ...
48
+ def get_private_key(self) -> str: ...
49
+ def get_public_key(self) -> str: ...
50
+ def to_json(self) -> str: ...
51
+
52
+ class NostrProfile(Profile):
53
+ public_key: PublicKey
54
+ profile_url: str
55
+ WEB_URL: ClassVar[str]
56
+ locations: Set[str]
57
+
58
+ def __init__(self, public_key: PublicKey) -> None: ...
59
+ @classmethod
60
+ def from_metadata(
61
+ cls, metadata: Metadata, public_key: PublicKey
62
+ ) -> "NostrProfile": ...
63
+ def add_location(self, location: str) -> None: ...
64
+ def get_public_key(self) -> str: ...
65
+ def get_locations(self) -> Set[str]: ...
66
+ def get_profile_url(self) -> str: ...
67
+ def get_zip_codes(self) -> List[str]: ...
68
+ def to_json(self) -> str: ...
69
+ def __eq__(self, other: object) -> bool: ...
70
+ def __hash__(self) -> int: ...
71
+
72
+ class MerchantProduct(BaseModel):
73
+ id: str
74
+ stall_id: str
75
+ name: str
76
+ description: str
77
+ images: List[str]
78
+ currency: str
79
+ price: float
80
+ quantity: int
81
+ shipping: List[ShippingCost]
82
+ categories: List[str]
83
+ specs: List[List[str]]
84
+
85
+ @classmethod
86
+ def from_product_data(cls, product_data: ProductData) -> "MerchantProduct": ...
87
+ def to_product_data(self) -> ProductData: ...
88
+ def to_dict(self) -> dict: ...
89
+
90
+ class MerchantStall(BaseModel):
91
+ id: str
92
+ name: str
93
+ description: str
94
+ currency: str
95
+ shipping: List[ShippingMethod]
96
+ geohash: str
97
+
98
+ @classmethod
99
+ def from_stall_data(cls, stall_data: StallData) -> "MerchantStall": ...
100
+ def get_geohash(self) -> str: ...
101
+ def set_geohash(self, geohash: str) -> None: ...
102
+ def to_dict(self) -> dict: ...
103
+ def to_stall_data(self) -> StallData: ...