agentstr 0.1.14__tar.gz → 0.1.16__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.
- agentstr-0.1.16/MANIFEST.in +7 -0
- {agentstr-0.1.14/src/agentstr.egg-info → agentstr-0.1.16}/PKG-INFO +6 -8
- {agentstr-0.1.14 → agentstr-0.1.16}/README.md +0 -4
- {agentstr-0.1.14 → agentstr-0.1.16}/pyproject.toml +10 -6
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/__init__.py +2 -1
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/buyer.py +48 -26
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/buyer.pyi +7 -3
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/merchant.py +2 -0
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/models.py +15 -4
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/models.pyi +5 -2
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/nostr.py +80 -5
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/nostr.pyi +19 -2
- {agentstr-0.1.14 → agentstr-0.1.16/src/agentstr.egg-info}/PKG-INFO +6 -8
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr.egg-info/SOURCES.txt +1 -5
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr.egg-info/requires.txt +4 -2
- agentstr-0.1.14/MANIFEST.in +0 -3
- agentstr-0.1.14/tests/test_buyer.py +0 -110
- agentstr-0.1.14/tests/test_merchant.py +0 -154
- agentstr-0.1.14/tests/test_nostr_integration.py +0 -98
- agentstr-0.1.14/tests/test_nostr_mocked.py +0 -107
- {agentstr-0.1.14 → agentstr-0.1.16}/LICENSE +0 -0
- {agentstr-0.1.14 → agentstr-0.1.16}/setup.cfg +0 -0
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/merchant.pyi +0 -0
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr/py.typed +0 -0
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr.egg-info/dependency_links.txt +0 -0
- {agentstr-0.1.14 → agentstr-0.1.16}/src/agentstr.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: agentstr
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.16
|
4
4
|
Summary: Tools for a Nostr agentic ecosystem
|
5
5
|
Author-email: Synvya <synvya@synvya.com>
|
6
6
|
License: MIT
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://www.synvya.com
|
|
8
8
|
Project-URL: Repository, https://github.com/synvya/agentstr
|
9
9
|
Project-URL: Documentation, https://github.com/synvya/agentstr#readme
|
10
10
|
Project-URL: BugTracker, https://github.com/synvya/agentstr/issues
|
11
|
-
Requires-Python: <3.13,>=3.
|
11
|
+
Requires-Python: <3.13,>=3.10
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: agno>=1.1.1
|
@@ -24,8 +24,10 @@ Requires-Dist: mypy>=1.0; extra == "dev"
|
|
24
24
|
Requires-Dist: pylint>=3.0; extra == "dev"
|
25
25
|
Provides-Extra: examples
|
26
26
|
Requires-Dist: python-dotenv>=1.0; extra == "examples"
|
27
|
-
Requires-Dist:
|
28
|
-
Requires-Dist:
|
27
|
+
Requires-Dist: psycopg[binary]>=3.2.5; extra == "examples"
|
28
|
+
Requires-Dist: sqlalchemy>=2.0.0; extra == "examples"
|
29
|
+
Requires-Dist: nest-asyncio>=1.6.0; extra == "examples"
|
30
|
+
Requires-Dist: pgvector>=0.3.6; extra == "examples"
|
29
31
|
Requires-Dist: fastapi>=0.110.0; extra == "examples"
|
30
32
|
Requires-Dist: uvicorn>=0.30.0; extra == "examples"
|
31
33
|
|
@@ -126,7 +128,3 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
|
|
126
128
|
- [Rust-Nostr](https://rust-nostr.org) - For their Python Nostr SDK
|
127
129
|
- [Nostr Protocol](https://github.com/nostr-protocol/nips) - For the protocol specification
|
128
130
|
|
129
|
-
This software includes the following software licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0):
|
130
|
-
- [DataStax Python Driver for Apache Cassandra](https://github.com/datastax/python-driver)
|
131
|
-
- [cassIO](https://github.com/CassioML/cassio). This library is not maintained anymore. We will need to replace it with a new library.
|
132
|
-
|
@@ -95,7 +95,3 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
|
|
95
95
|
- [Rust-Nostr](https://rust-nostr.org) - For their Python Nostr SDK
|
96
96
|
- [Nostr Protocol](https://github.com/nostr-protocol/nips) - For the protocol specification
|
97
97
|
|
98
|
-
This software includes the following software licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0):
|
99
|
-
- [DataStax Python Driver for Apache Cassandra](https://github.com/datastax/python-driver)
|
100
|
-
- [cassIO](https://github.com/CassioML/cassio). This library is not maintained anymore. We will need to replace it with a new library.
|
101
|
-
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "agentstr"
|
7
|
-
version = "0.1.
|
7
|
+
version = "0.1.16"
|
8
8
|
description = "Tools for a Nostr agentic ecosystem"
|
9
9
|
readme = "README.md"
|
10
|
-
requires-python = ">=3.
|
10
|
+
requires-python = ">=3.10, <3.13"
|
11
11
|
license = { text = "MIT" }
|
12
12
|
authors = [
|
13
13
|
{name = "Synvya", email = "synvya@synvya.com"}
|
@@ -30,8 +30,10 @@ dev = [
|
|
30
30
|
]
|
31
31
|
examples = [
|
32
32
|
"python-dotenv>=1.0",
|
33
|
-
"
|
34
|
-
"
|
33
|
+
"psycopg[binary]>=3.2.5",
|
34
|
+
"sqlalchemy>=2.0.0",
|
35
|
+
"nest-asyncio>=1.6.0",
|
36
|
+
"pgvector>=0.3.6",
|
35
37
|
"fastapi>=0.110.0",
|
36
38
|
"uvicorn>=0.30.0",
|
37
39
|
]
|
@@ -45,6 +47,8 @@ BugTracker = "https://github.com/synvya/agentstr/issues"
|
|
45
47
|
[tool.setuptools]
|
46
48
|
package-dir = {"" = "src"}
|
47
49
|
packages = ["agentstr"]
|
50
|
+
exclude-package-data = {"*" = ["tests/*", "tests/**/*"]}
|
51
|
+
|
48
52
|
|
49
53
|
[tool.black]
|
50
54
|
line-length = 88
|
@@ -58,7 +62,7 @@ src_paths = ["src", "tests", "examples"]
|
|
58
62
|
multi_line_output = 3
|
59
63
|
|
60
64
|
[tool.mypy]
|
61
|
-
python_version = "3.
|
65
|
+
python_version = "3.10"
|
62
66
|
warn_return_any = true
|
63
67
|
warn_unused_configs = true
|
64
68
|
mypy_path = "src"
|
@@ -76,4 +80,4 @@ agentstr = ["py.typed"]
|
|
76
80
|
asyncio_mode = "auto"
|
77
81
|
markers = [
|
78
82
|
"asyncio: mark test as async",
|
79
|
-
]
|
83
|
+
]
|
@@ -5,7 +5,7 @@ AgentStr: Nostr extension for Agno AI agents
|
|
5
5
|
import importlib.metadata
|
6
6
|
import logging
|
7
7
|
|
8
|
-
from nostr_sdk import ShippingCost, ShippingMethod, Timestamp # type: ignore
|
8
|
+
from nostr_sdk import PublicKey, ShippingCost, ShippingMethod, Timestamp # type: ignore
|
9
9
|
|
10
10
|
from agentstr.nostr import EventId, Keys, Kind, NostrClient, generate_and_save_keys
|
11
11
|
|
@@ -43,6 +43,7 @@ __all__ = [
|
|
43
43
|
"NostrClient",
|
44
44
|
"generate_and_save_keys",
|
45
45
|
"Timestamp",
|
46
|
+
"PublicKey",
|
46
47
|
# Models
|
47
48
|
"AgentProfile",
|
48
49
|
"NostrProfile",
|
@@ -4,7 +4,6 @@ Module implementing the BuyerTools Toolkit for Agno agents.
|
|
4
4
|
|
5
5
|
import json
|
6
6
|
import logging
|
7
|
-
from uuid import uuid4
|
8
7
|
|
9
8
|
from pydantic import ConfigDict
|
10
9
|
|
@@ -82,6 +81,8 @@ class BuyerTools(Toolkit):
|
|
82
81
|
self._nostr_client = NostrClient(relay, buyer_profile.get_private_key())
|
83
82
|
|
84
83
|
# Register methods
|
84
|
+
self.register(self.download_all_sellers)
|
85
|
+
self.register(self.download_sellers_from_marketplace)
|
85
86
|
self.register(self.find_seller_by_name)
|
86
87
|
self.register(self.find_seller_by_public_key)
|
87
88
|
self.register(self.find_sellers_by_location)
|
@@ -91,7 +92,6 @@ class BuyerTools(Toolkit):
|
|
91
92
|
self.register(self.get_seller_products)
|
92
93
|
self.register(self.get_seller_count)
|
93
94
|
self.register(self.get_sellers)
|
94
|
-
self.register(self.refresh_sellers)
|
95
95
|
self.register(self.purchase_product)
|
96
96
|
|
97
97
|
def purchase_product(self, product: str) -> str:
|
@@ -107,6 +107,30 @@ class BuyerTools(Toolkit):
|
|
107
107
|
{"status": "success", "message": f"Product {product} purchased"}
|
108
108
|
)
|
109
109
|
|
110
|
+
def download_all_sellers(self) -> str:
|
111
|
+
"""Download all sellers from the Nostr relay.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
str: JSON string with status and count of sellers refreshed
|
115
|
+
"""
|
116
|
+
self._download_all_sellers()
|
117
|
+
response = json.dumps({"status": "success", "count": len(self.sellers)})
|
118
|
+
return response
|
119
|
+
|
120
|
+
def download_sellers_from_marketplace(self, owner: str, name: str) -> str:
|
121
|
+
"""Download sellers from a marketplace.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
owner: bech32 encoded public key of the owner of the marketplace
|
125
|
+
name: name of the marketplace to download sellers from
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
str: JSON string with status and count of sellers downloaded
|
129
|
+
"""
|
130
|
+
self._download_sellers_from_marketplace(PublicKey.parse(owner), name)
|
131
|
+
response = json.dumps({"status": "success", "count": len(self.sellers)})
|
132
|
+
return response
|
133
|
+
|
110
134
|
def find_seller_by_name(self, name: str) -> str:
|
111
135
|
"""Find a seller by name.
|
112
136
|
|
@@ -256,37 +280,23 @@ class BuyerTools(Toolkit):
|
|
256
280
|
return response
|
257
281
|
|
258
282
|
def get_sellers(self) -> str:
|
259
|
-
"""Get the list of sellers.
|
260
|
-
|
261
|
-
|
262
|
-
To get a fresh list of sellers, call refresh_sellers() sellers first.
|
283
|
+
"""Get the list of sellers cached in the BuyerTools instance.
|
284
|
+
To get a fresh list of sellers, call download_all_sellers() or
|
285
|
+
get_sellers_from_marketplace() first.
|
263
286
|
|
264
287
|
Returns:
|
265
288
|
str: list of sellers json strings
|
266
289
|
"""
|
267
|
-
if not self.sellers:
|
268
|
-
|
290
|
+
# if not self.sellers:
|
291
|
+
# self._refresh_sellers()
|
269
292
|
response = json.dumps([seller.to_json() for seller in self.sellers])
|
270
293
|
return response
|
271
294
|
|
272
|
-
def
|
273
|
-
"""Refresh the list of sellers.
|
274
|
-
|
275
|
-
Returns:
|
276
|
-
str: JSON string with status and count of sellers refreshed
|
277
|
-
"""
|
278
|
-
self._refresh_sellers()
|
279
|
-
response = json.dumps({"status": "success", "count": len(self.sellers)})
|
280
|
-
return response
|
281
|
-
|
282
|
-
def _refresh_sellers(self) -> None:
|
295
|
+
def _download_all_sellers(self) -> None:
|
283
296
|
"""
|
284
|
-
Internal fucntion to retrieve a new
|
285
|
-
The old
|
297
|
+
Internal fucntion to retrieve a new set of sellers from the Nostr relay.
|
298
|
+
The old set is discarded and the new set only contains unique sellers
|
286
299
|
currently stored at the relay.
|
287
|
-
|
288
|
-
Returns:
|
289
|
-
List[NostrProfile]: List of Nostr profiles of all sellers.
|
290
300
|
"""
|
291
301
|
sellers = self._nostr_client.retrieve_sellers()
|
292
302
|
if len(sellers) == 0:
|
@@ -296,10 +306,22 @@ class BuyerTools(Toolkit):
|
|
296
306
|
|
297
307
|
self.sellers = sellers
|
298
308
|
|
309
|
+
def _download_sellers_from_marketplace(self, owner: PublicKey, name: str) -> None:
|
310
|
+
"""
|
311
|
+
Internal function to download sellers from a marketplace and store them as
|
312
|
+
the new set of sellers for the BuyerTools instance.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
owner: PublicKey of the owner of the marketplace
|
316
|
+
name: name of the marketplace to download sellers from
|
317
|
+
"""
|
318
|
+
sellers = self._nostr_client.retrieve_marketplace(owner, name)
|
319
|
+
self.sellers = sellers
|
320
|
+
|
299
321
|
def _store_response_in_knowledge_base(self, response: str) -> None:
|
300
322
|
doc = Document(
|
301
|
-
id=str(uuid4()),
|
323
|
+
# id=str(uuid4()),
|
302
324
|
content=response,
|
303
325
|
)
|
304
|
-
|
326
|
+
print(f"Document length: {len(doc.content.split())} words")
|
305
327
|
self.knowledge_base.load_documents([doc]) # Store response in Cassandra
|
@@ -5,7 +5,7 @@ from agno.agent import AgentKnowledge
|
|
5
5
|
from agno.tools import Toolkit
|
6
6
|
|
7
7
|
from agentstr.models import AgentProfile, NostrProfile
|
8
|
-
from agentstr.nostr import NostrClient
|
8
|
+
from agentstr.nostr import NostrClient, PublicKey
|
9
9
|
|
10
10
|
class BuyerTools(Toolkit):
|
11
11
|
logger: ClassVar[Logger]
|
@@ -16,6 +16,8 @@ class BuyerTools(Toolkit):
|
|
16
16
|
def __init__(
|
17
17
|
self, knowledge_base: AgentKnowledge, buyer_profile: AgentProfile, relay: str
|
18
18
|
) -> None: ...
|
19
|
+
def download_sellers_from_marketplace(self, owner: PublicKey, name: str) -> str: ...
|
20
|
+
def download_all_sellers(self) -> str: ...
|
19
21
|
def find_seller_by_name(self, name: str) -> str: ...
|
20
22
|
def find_seller_by_public_key(self, public_key: str) -> str: ...
|
21
23
|
def find_sellers_by_location(self, location: str) -> str: ...
|
@@ -27,6 +29,8 @@ class BuyerTools(Toolkit):
|
|
27
29
|
def get_seller_products(self, public_key: str) -> str: ...
|
28
30
|
def get_sellers(self) -> str: ...
|
29
31
|
def purchase_product(self, product: str) -> str: ...
|
30
|
-
def
|
31
|
-
def
|
32
|
+
def _download_all_sellers(self) -> None: ...
|
33
|
+
def _download_sellers_from_marketplace(
|
34
|
+
self, owner: PublicKey, name: str
|
35
|
+
) -> None: ...
|
32
36
|
def _store_response_in_knowledge_base(self, response: str) -> None: ...
|
@@ -437,6 +437,8 @@ class MerchantTools(Toolkit):
|
|
437
437
|
self.merchant_profile.get_name(),
|
438
438
|
self.merchant_profile.get_about(),
|
439
439
|
self.merchant_profile.get_picture(),
|
440
|
+
self.merchant_profile.get_banner(),
|
441
|
+
self.merchant_profile.get_website(),
|
440
442
|
)
|
441
443
|
return json.dumps(event_id.__dict__)
|
442
444
|
except RuntimeError as e:
|
@@ -31,6 +31,7 @@ class Profile:
|
|
31
31
|
|
32
32
|
def __init__(self) -> None:
|
33
33
|
self.about = ""
|
34
|
+
self.banner = ""
|
34
35
|
self.display_name = ""
|
35
36
|
self.name = ""
|
36
37
|
self.picture = ""
|
@@ -39,6 +40,9 @@ class Profile:
|
|
39
40
|
def get_about(self) -> str:
|
40
41
|
return self.about
|
41
42
|
|
43
|
+
def get_banner(self) -> str:
|
44
|
+
return self.banner
|
45
|
+
|
42
46
|
def get_display_name(self) -> str:
|
43
47
|
return self.display_name
|
44
48
|
|
@@ -54,6 +58,9 @@ class Profile:
|
|
54
58
|
def set_about(self, about: str) -> None:
|
55
59
|
self.about = about
|
56
60
|
|
61
|
+
def set_banner(self, banner: str) -> None:
|
62
|
+
self.banner = banner
|
63
|
+
|
57
64
|
def set_display_name(self, display_name: str) -> None:
|
58
65
|
self.display_name = display_name
|
59
66
|
|
@@ -71,6 +78,7 @@ class Profile:
|
|
71
78
|
"name": self.name,
|
72
79
|
"display_name": self.display_name,
|
73
80
|
"about": self.about,
|
81
|
+
"banner": self.banner,
|
74
82
|
"picture": self.picture,
|
75
83
|
"website": self.website,
|
76
84
|
}
|
@@ -85,12 +93,12 @@ class NostrProfile(Profile):
|
|
85
93
|
a third party profile and therefore it only has a public key.
|
86
94
|
"""
|
87
95
|
|
88
|
-
|
96
|
+
PROFILE_URL_PREFIX: str = "https://primal.net/p/"
|
89
97
|
|
90
98
|
def __init__(self, public_key: PublicKey) -> None:
|
91
99
|
super().__init__()
|
92
100
|
self.public_key = public_key
|
93
|
-
self.profile_url = self.
|
101
|
+
self.profile_url = self.PROFILE_URL_PREFIX + self.public_key.to_bech32()
|
94
102
|
# Initialize the locations set here, per-instance
|
95
103
|
self.locations: set[str] = set()
|
96
104
|
|
@@ -98,6 +106,7 @@ class NostrProfile(Profile):
|
|
98
106
|
def from_metadata(cls, metadata: Metadata, public_key: PublicKey) -> "NostrProfile":
|
99
107
|
profile = cls(public_key)
|
100
108
|
profile.set_about(metadata.get_about())
|
109
|
+
profile.set_banner(metadata.get_banner())
|
101
110
|
profile.set_display_name(metadata.get_display_name())
|
102
111
|
profile.set_name(metadata.get_name())
|
103
112
|
profile.set_picture(metadata.get_picture())
|
@@ -171,6 +180,7 @@ class NostrProfile(Profile):
|
|
171
180
|
"name": self.name,
|
172
181
|
"display_name": self.display_name,
|
173
182
|
"about": self.about,
|
183
|
+
"banner": self.banner,
|
174
184
|
"picture": self.picture,
|
175
185
|
"website": self.website,
|
176
186
|
}
|
@@ -181,17 +191,18 @@ class AgentProfile(Profile):
|
|
181
191
|
AgentProfile is a Profile that is used to represent an agent.
|
182
192
|
"""
|
183
193
|
|
184
|
-
|
194
|
+
PROFILE_URL_PREFIX: str = "https://primal.net/p/"
|
185
195
|
|
186
196
|
def __init__(self, keys: Keys) -> None:
|
187
197
|
super().__init__()
|
188
198
|
self.keys = keys
|
189
|
-
self.profile_url = self.
|
199
|
+
self.profile_url = self.PROFILE_URL_PREFIX + self.keys.public_key().to_bech32()
|
190
200
|
|
191
201
|
@classmethod
|
192
202
|
def from_metadata(cls, metadata: Metadata, keys: Keys) -> "AgentProfile":
|
193
203
|
profile = cls(keys)
|
194
204
|
profile.set_about(metadata.get_about())
|
205
|
+
profile.set_banner(metadata.get_banner())
|
195
206
|
profile.set_display_name(metadata.get_display_name())
|
196
207
|
profile.set_name(metadata.get_name())
|
197
208
|
profile.set_picture(metadata.get_picture())
|
@@ -19,6 +19,7 @@ class Profile:
|
|
19
19
|
|
20
20
|
logger: ClassVar[Logger]
|
21
21
|
about: str
|
22
|
+
banner: str
|
22
23
|
display_name: str
|
23
24
|
name: str
|
24
25
|
picture: str
|
@@ -26,11 +27,13 @@ class Profile:
|
|
26
27
|
|
27
28
|
def __init__(self) -> None: ...
|
28
29
|
def get_about(self) -> str: ...
|
30
|
+
def get_banner(self) -> str: ...
|
29
31
|
def get_display_name(self) -> str: ...
|
30
32
|
def get_name(self) -> str: ...
|
31
33
|
def get_picture(self) -> str: ...
|
32
34
|
def get_website(self) -> str: ...
|
33
35
|
def set_about(self, about: str) -> None: ...
|
36
|
+
def set_banner(self, banner: str) -> None: ...
|
34
37
|
def set_display_name(self, display_name: str) -> None: ...
|
35
38
|
def set_name(self, name: str) -> None: ...
|
36
39
|
def set_picture(self, picture: str) -> None: ...
|
@@ -38,7 +41,7 @@ class Profile:
|
|
38
41
|
def to_json(self) -> str: ...
|
39
42
|
|
40
43
|
class AgentProfile(Profile):
|
41
|
-
|
44
|
+
PROFILE_URL_PREFIX: ClassVar[str]
|
42
45
|
profile_url: str
|
43
46
|
keys: Keys
|
44
47
|
|
@@ -52,7 +55,7 @@ class AgentProfile(Profile):
|
|
52
55
|
class NostrProfile(Profile):
|
53
56
|
public_key: PublicKey
|
54
57
|
profile_url: str
|
55
|
-
|
58
|
+
PROFILE_URL_PREFIX: ClassVar[str]
|
56
59
|
locations: Set[str]
|
57
60
|
|
58
61
|
def __init__(self, public_key: PublicKey) -> None: ...
|
@@ -144,14 +144,23 @@ class NostrClient:
|
|
144
144
|
except Exception as e:
|
145
145
|
raise RuntimeError(f"Failed to publish product: {e}") from e
|
146
146
|
|
147
|
-
def publish_profile(
|
147
|
+
def publish_profile(
|
148
|
+
self,
|
149
|
+
name: str,
|
150
|
+
about: str,
|
151
|
+
picture: str,
|
152
|
+
banner: str,
|
153
|
+
website: Optional[str] = None,
|
154
|
+
) -> EventId:
|
148
155
|
"""
|
149
156
|
Publish a Nostr profile with event kind 0
|
150
157
|
|
151
158
|
Args:
|
152
159
|
name: name of the Nostr profile
|
153
160
|
about: brief description about the profile
|
154
|
-
picture: url to a
|
161
|
+
picture: url to a file with a picture for the profile
|
162
|
+
banner: url to a file with a banner for the profile
|
163
|
+
website: optional url to a website for the profile
|
155
164
|
|
156
165
|
Returns:
|
157
166
|
EventId: event id if successful
|
@@ -160,7 +169,9 @@ class NostrClient:
|
|
160
169
|
RuntimeError: if the profile can't be published
|
161
170
|
"""
|
162
171
|
# Run the async publishing function synchronously
|
163
|
-
return asyncio.run(
|
172
|
+
return asyncio.run(
|
173
|
+
self._async_publish_profile(name, about, picture, banner, website)
|
174
|
+
)
|
164
175
|
|
165
176
|
def publish_stall(self, stall: MerchantStall) -> EventId:
|
166
177
|
"""Publish a stall to nostr
|
@@ -179,6 +190,41 @@ class NostrClient:
|
|
179
190
|
except Exception as e:
|
180
191
|
raise RuntimeError(f"Failed to publish stall: {e}") from e
|
181
192
|
|
193
|
+
def retrieve_marketplace(self, owner: PublicKey, name: str) -> set[NostrProfile]:
|
194
|
+
"""
|
195
|
+
Retrieve all sellers from the marketplace.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
owner: PublicKey of the owner of the marketplace
|
199
|
+
name: name of the marketplace
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
set[NostrProfile]: set of seller profiles.
|
203
|
+
(skips authors with missing metadata)
|
204
|
+
"""
|
205
|
+
events_filter = Filter().kind(Kind(30019)).authors([owner])
|
206
|
+
try:
|
207
|
+
events = asyncio.run(self._async_retrieve_events(events_filter))
|
208
|
+
except Exception as e:
|
209
|
+
raise RuntimeError(f"Failed to retrieve marketplace: {e}") from e
|
210
|
+
|
211
|
+
events_list = events.to_vec()
|
212
|
+
merchants_dict: Dict[PublicKey, NostrProfile] = {}
|
213
|
+
|
214
|
+
for event in events_list:
|
215
|
+
content = json.loads(event.content())
|
216
|
+
if content.get("name") == name:
|
217
|
+
merchants = content.get("merchants", [])
|
218
|
+
for merchant in merchants:
|
219
|
+
try:
|
220
|
+
public_key = PublicKey.parse(merchant)
|
221
|
+
profile = asyncio.run(self._async_retrieve_profile(public_key))
|
222
|
+
merchants_dict[public_key] = profile
|
223
|
+
except RuntimeError:
|
224
|
+
continue
|
225
|
+
|
226
|
+
return set(merchants_dict.values())
|
227
|
+
|
182
228
|
def retrieve_products_from_seller(self, seller: PublicKey) -> List[MerchantProduct]:
|
183
229
|
"""
|
184
230
|
Retrieve all products from a given seller.
|
@@ -451,7 +497,12 @@ class NostrClient:
|
|
451
497
|
return await self._async_publish_event(good_event_builder)
|
452
498
|
|
453
499
|
async def _async_publish_profile(
|
454
|
-
self,
|
500
|
+
self,
|
501
|
+
name: str,
|
502
|
+
about: str,
|
503
|
+
picture: str,
|
504
|
+
banner: str,
|
505
|
+
website: Optional[str] = None,
|
455
506
|
) -> EventId:
|
456
507
|
"""
|
457
508
|
Asynchronous function to publish a Nostr profile with event kind 0
|
@@ -459,7 +510,9 @@ class NostrClient:
|
|
459
510
|
Args:
|
460
511
|
name: name of the Nostr profile
|
461
512
|
about: brief description about the profile
|
462
|
-
picture: url to a
|
513
|
+
picture: url to a file with a picture for the profile
|
514
|
+
banner: url to a file with a banner for the profile
|
515
|
+
website: optional url to a website for the profile
|
463
516
|
|
464
517
|
Returns:
|
465
518
|
EventId: event id if successful
|
@@ -469,8 +522,13 @@ class NostrClient:
|
|
469
522
|
"""
|
470
523
|
metadata_content = Metadata().set_name(name)
|
471
524
|
metadata_content = metadata_content.set_about(about)
|
525
|
+
print(f"Banner: {banner}")
|
526
|
+
metadata_content = metadata_content.set_banner(banner)
|
472
527
|
metadata_content = metadata_content.set_picture(picture)
|
473
528
|
|
529
|
+
if website:
|
530
|
+
metadata_content = metadata_content.set_website(website)
|
531
|
+
|
474
532
|
event_builder = EventBuilder.metadata(metadata_content)
|
475
533
|
return await self._async_publish_event(event_builder)
|
476
534
|
|
@@ -529,6 +587,23 @@ class NostrClient:
|
|
529
587
|
except Exception as e:
|
530
588
|
raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
|
531
589
|
|
590
|
+
async def _async_retrieve_events(self, events_filter: Filter) -> Events:
|
591
|
+
"""
|
592
|
+
Asynchronous function to retrieve events from the relay
|
593
|
+
"""
|
594
|
+
try:
|
595
|
+
await self._async_connect()
|
596
|
+
except Exception as e:
|
597
|
+
raise RuntimeError("Unable to connect to the relay") from e
|
598
|
+
|
599
|
+
try:
|
600
|
+
events = await self.client.fetch_events_from(
|
601
|
+
urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
|
602
|
+
)
|
603
|
+
return events
|
604
|
+
except Exception as e:
|
605
|
+
raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
|
606
|
+
|
532
607
|
async def _async_retrieve_products_from_seller(self, seller: PublicKey) -> Events:
|
533
608
|
"""
|
534
609
|
Asynchronous function to retrieve the products for a given author
|
@@ -18,6 +18,7 @@ from nostr_sdk import ( # type: ignore
|
|
18
18
|
EventBuilder,
|
19
19
|
EventId,
|
20
20
|
Events,
|
21
|
+
Filter,
|
21
22
|
Keys,
|
22
23
|
Kind,
|
23
24
|
Metadata,
|
@@ -68,8 +69,18 @@ class NostrClient:
|
|
68
69
|
def publish_event(self, event_builder: EventBuilder) -> EventId: ...
|
69
70
|
def publish_note(self, text: str) -> EventId: ...
|
70
71
|
def publish_product(self, product: MerchantProduct) -> EventId: ...
|
71
|
-
def publish_profile(
|
72
|
+
def publish_profile(
|
73
|
+
self,
|
74
|
+
name: str,
|
75
|
+
about: str,
|
76
|
+
picture: str,
|
77
|
+
banner: str,
|
78
|
+
website: Optional[str] = None,
|
79
|
+
) -> EventId: ...
|
72
80
|
def publish_stall(self, stall: MerchantStall) -> EventId: ...
|
81
|
+
def retrieve_marketplace(
|
82
|
+
self, owner: PublicKey, name: str
|
83
|
+
) -> set[NostrProfile]: ...
|
73
84
|
def retrieve_products_from_seller(
|
74
85
|
self, seller: PublicKey
|
75
86
|
) -> List[MerchantProduct]: ...
|
@@ -83,10 +94,16 @@ class NostrClient:
|
|
83
94
|
async def _async_publish_note(self, text: str) -> EventId: ...
|
84
95
|
async def _async_publish_product(self, product: MerchantProduct) -> EventId: ...
|
85
96
|
async def _async_publish_profile(
|
86
|
-
self,
|
97
|
+
self,
|
98
|
+
name: str,
|
99
|
+
about: str,
|
100
|
+
picture: str,
|
101
|
+
banner: str,
|
102
|
+
website: Optional[str] = None,
|
87
103
|
) -> EventId: ...
|
88
104
|
async def _async_publish_stall(self, stall: MerchantStall) -> EventId: ...
|
89
105
|
async def _async_retrieve_all_stalls(self) -> Events: ...
|
106
|
+
async def _async_retrieve_events(self, events_filter: Filter) -> Events: ...
|
90
107
|
async def _async_retrieve_products_from_seller(
|
91
108
|
self, seller: PublicKey
|
92
109
|
) -> Events: ...
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: agentstr
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.16
|
4
4
|
Summary: Tools for a Nostr agentic ecosystem
|
5
5
|
Author-email: Synvya <synvya@synvya.com>
|
6
6
|
License: MIT
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://www.synvya.com
|
|
8
8
|
Project-URL: Repository, https://github.com/synvya/agentstr
|
9
9
|
Project-URL: Documentation, https://github.com/synvya/agentstr#readme
|
10
10
|
Project-URL: BugTracker, https://github.com/synvya/agentstr/issues
|
11
|
-
Requires-Python: <3.13,>=3.
|
11
|
+
Requires-Python: <3.13,>=3.10
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: agno>=1.1.1
|
@@ -24,8 +24,10 @@ Requires-Dist: mypy>=1.0; extra == "dev"
|
|
24
24
|
Requires-Dist: pylint>=3.0; extra == "dev"
|
25
25
|
Provides-Extra: examples
|
26
26
|
Requires-Dist: python-dotenv>=1.0; extra == "examples"
|
27
|
-
Requires-Dist:
|
28
|
-
Requires-Dist:
|
27
|
+
Requires-Dist: psycopg[binary]>=3.2.5; extra == "examples"
|
28
|
+
Requires-Dist: sqlalchemy>=2.0.0; extra == "examples"
|
29
|
+
Requires-Dist: nest-asyncio>=1.6.0; extra == "examples"
|
30
|
+
Requires-Dist: pgvector>=0.3.6; extra == "examples"
|
29
31
|
Requires-Dist: fastapi>=0.110.0; extra == "examples"
|
30
32
|
Requires-Dist: uvicorn>=0.30.0; extra == "examples"
|
31
33
|
|
@@ -126,7 +128,3 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
|
|
126
128
|
- [Rust-Nostr](https://rust-nostr.org) - For their Python Nostr SDK
|
127
129
|
- [Nostr Protocol](https://github.com/nostr-protocol/nips) - For the protocol specification
|
128
130
|
|
129
|
-
This software includes the following software licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0):
|
130
|
-
- [DataStax Python Driver for Apache Cassandra](https://github.com/datastax/python-driver)
|
131
|
-
- [cassIO](https://github.com/CassioML/cassio). This library is not maintained anymore. We will need to replace it with a new library.
|
132
|
-
|
@@ -16,8 +16,4 @@ src/agentstr.egg-info/PKG-INFO
|
|
16
16
|
src/agentstr.egg-info/SOURCES.txt
|
17
17
|
src/agentstr.egg-info/dependency_links.txt
|
18
18
|
src/agentstr.egg-info/requires.txt
|
19
|
-
src/agentstr.egg-info/top_level.txt
|
20
|
-
tests/test_buyer.py
|
21
|
-
tests/test_merchant.py
|
22
|
-
tests/test_nostr_integration.py
|
23
|
-
tests/test_nostr_mocked.py
|
19
|
+
src/agentstr.egg-info/top_level.txt
|
agentstr-0.1.14/MANIFEST.in
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains tests for the BuyerTools class.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
from typing import List
|
7
|
-
from unittest.mock import patch
|
8
|
-
|
9
|
-
from agentstr.buyer import BuyerTools
|
10
|
-
from agentstr.models import AgentProfile, MerchantProduct, MerchantStall, NostrProfile
|
11
|
-
from agentstr.nostr import Keys, PublicKey
|
12
|
-
|
13
|
-
|
14
|
-
def test_buyer_profile_creation(
|
15
|
-
buyer_profile: AgentProfile,
|
16
|
-
buyer_profile_name: str,
|
17
|
-
buyer_profile_about: str,
|
18
|
-
buyer_profile_picture: str,
|
19
|
-
) -> None:
|
20
|
-
"""Test the creation of a buyer profile"""
|
21
|
-
assert buyer_profile.get_name() == buyer_profile_name
|
22
|
-
assert buyer_profile.get_about() == buyer_profile_about
|
23
|
-
assert buyer_profile.get_picture() == buyer_profile_picture
|
24
|
-
|
25
|
-
|
26
|
-
def test_find_sellers_by_location(
|
27
|
-
buyer_tools: BuyerTools, merchant_location: str, merchant_profile_name: str
|
28
|
-
) -> None:
|
29
|
-
"""Test the finding of sellers by location"""
|
30
|
-
with patch(
|
31
|
-
"agentstr.buyer._map_location_to_geohash"
|
32
|
-
) as mock_map_location_to_geohash:
|
33
|
-
mock_map_location_to_geohash.return_value = "000000000"
|
34
|
-
|
35
|
-
result = buyer_tools.find_sellers_by_location(merchant_location)
|
36
|
-
assert result is not None
|
37
|
-
assert merchant_profile_name in result
|
38
|
-
|
39
|
-
|
40
|
-
def test_find_seller_by_name(
|
41
|
-
buyer_tools: BuyerTools,
|
42
|
-
merchant_profile_name: str,
|
43
|
-
) -> None:
|
44
|
-
"""Test the finding of a seller by name"""
|
45
|
-
result = buyer_tools.find_seller_by_name(merchant_profile_name)
|
46
|
-
assert result is not None
|
47
|
-
assert merchant_profile_name in result
|
48
|
-
|
49
|
-
|
50
|
-
def test_find_seller_by_public_key(
|
51
|
-
buyer_tools: BuyerTools,
|
52
|
-
merchant_keys: Keys,
|
53
|
-
seller_nostr_profile: NostrProfile,
|
54
|
-
) -> None:
|
55
|
-
"""Test the finding of a seller by public key"""
|
56
|
-
with patch.object(
|
57
|
-
buyer_tools, "find_seller_by_public_key"
|
58
|
-
) as mock_find_seller_by_public_key:
|
59
|
-
mock_find_seller_by_public_key.return_value = seller_nostr_profile.to_json()
|
60
|
-
|
61
|
-
result = buyer_tools.find_seller_by_public_key(
|
62
|
-
merchant_keys.public_key().to_bech32()
|
63
|
-
)
|
64
|
-
assert result is not None
|
65
|
-
assert merchant_keys.public_key().to_bech32() in result
|
66
|
-
|
67
|
-
|
68
|
-
def test_get_seller_stalls(
|
69
|
-
buyer_tools: BuyerTools,
|
70
|
-
seller_nostr_profile: NostrProfile,
|
71
|
-
merchant_stalls: List[MerchantStall],
|
72
|
-
) -> None:
|
73
|
-
"""Test the retrieval of a seller's stalls"""
|
74
|
-
with patch.object(
|
75
|
-
buyer_tools.get_nostr_client(), "retrieve_stalls_from_seller"
|
76
|
-
) as mock_get_seller_stalls:
|
77
|
-
stall_data = [
|
78
|
-
merchant_stall.to_stall_data() for merchant_stall in merchant_stalls
|
79
|
-
]
|
80
|
-
mock_get_seller_stalls.return_value = stall_data
|
81
|
-
|
82
|
-
result = buyer_tools.get_seller_stalls(seller_nostr_profile.get_public_key())
|
83
|
-
assert result is not None
|
84
|
-
|
85
|
-
|
86
|
-
def test_get_seller_products(
|
87
|
-
buyer_tools: BuyerTools,
|
88
|
-
seller_nostr_profile: NostrProfile,
|
89
|
-
merchant_products: List[MerchantProduct],
|
90
|
-
) -> None:
|
91
|
-
"""Test the retrieval of a seller's products"""
|
92
|
-
with patch.object(
|
93
|
-
buyer_tools.get_nostr_client(),
|
94
|
-
"retrieve_products_from_seller",
|
95
|
-
return_value=merchant_products,
|
96
|
-
) as mock_get_seller_products:
|
97
|
-
result = buyer_tools.get_seller_products(seller_nostr_profile.get_public_key())
|
98
|
-
assert isinstance(result, str) # Ensure it's a JSON string
|
99
|
-
|
100
|
-
# ✅ Verify that the mocked method was called
|
101
|
-
mock_get_seller_products.assert_called_once_with(
|
102
|
-
PublicKey.parse(seller_nostr_profile.get_public_key())
|
103
|
-
)
|
104
|
-
|
105
|
-
products = json.loads(result) # Convert JSON string back to a Python list
|
106
|
-
products = json.loads(result) # Convert JSON string back to a Python list
|
107
|
-
assert isinstance(products, list) # Ensure it's a list
|
108
|
-
assert len(products) > 0 # Ensure the list is not empty
|
109
|
-
assert isinstance(products[0], dict) # Ensure the first item is a dictionary
|
110
|
-
assert "name" in products[0] # Ensure "name" key exists in the first product
|
@@ -1,154 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains tests for the MerchantTools class.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import itertools
|
6
|
-
import json
|
7
|
-
from typing import List
|
8
|
-
from unittest.mock import patch
|
9
|
-
|
10
|
-
from agentstr.merchant import MerchantTools
|
11
|
-
from agentstr.models import MerchantProduct, MerchantStall
|
12
|
-
from agentstr.nostr import EventId
|
13
|
-
|
14
|
-
|
15
|
-
def test_merchant_initialization(
|
16
|
-
relay: str,
|
17
|
-
merchant_tools: MerchantTools,
|
18
|
-
merchant_stalls: List[MerchantStall],
|
19
|
-
merchant_products: List[MerchantProduct],
|
20
|
-
) -> None:
|
21
|
-
"""Test merchant initialization"""
|
22
|
-
assert merchant_tools.get_profile() is not None
|
23
|
-
assert merchant_tools.get_relay() == relay
|
24
|
-
|
25
|
-
products = json.loads(merchant_tools.get_products())
|
26
|
-
assert len(products) == len(merchant_products)
|
27
|
-
|
28
|
-
stalls = json.loads(merchant_tools.get_stalls())
|
29
|
-
assert len(stalls) == len(merchant_stalls)
|
30
|
-
assert stalls[0]["name"] == merchant_stalls[0].name
|
31
|
-
|
32
|
-
|
33
|
-
def test_publish_product_by_name(
|
34
|
-
merchant_tools: MerchantTools,
|
35
|
-
product_event_ids: List[EventId],
|
36
|
-
merchant_products: List[MerchantProduct],
|
37
|
-
) -> None:
|
38
|
-
"""Test publishing a product by name"""
|
39
|
-
with patch.object(
|
40
|
-
merchant_tools.get_nostr_client(), "publish_product"
|
41
|
-
) as mock_publish:
|
42
|
-
mock_publish.return_value = product_event_ids[0]
|
43
|
-
|
44
|
-
result = json.loads(
|
45
|
-
merchant_tools.publish_product_by_name(merchant_products[0].name)
|
46
|
-
)
|
47
|
-
assert result["status"] == "success"
|
48
|
-
assert result["product_name"] == merchant_products[0].name
|
49
|
-
|
50
|
-
result = json.loads(
|
51
|
-
merchant_tools.publish_product_by_name(
|
52
|
-
json.dumps({"name": merchant_products[0].name})
|
53
|
-
)
|
54
|
-
)
|
55
|
-
assert result["status"] == "success"
|
56
|
-
assert result["product_name"] == merchant_products[0].name
|
57
|
-
|
58
|
-
|
59
|
-
def test_publish_stall_by_name(
|
60
|
-
merchant_tools: MerchantTools,
|
61
|
-
stall_event_ids: List[EventId],
|
62
|
-
merchant_stalls: List[MerchantStall],
|
63
|
-
) -> None:
|
64
|
-
"""Test publishing a stall by name"""
|
65
|
-
with patch.object(
|
66
|
-
merchant_tools.get_nostr_client(), "publish_stall"
|
67
|
-
) as mock_publish:
|
68
|
-
mock_publish.return_value = stall_event_ids[0]
|
69
|
-
|
70
|
-
result = json.loads(
|
71
|
-
merchant_tools.publish_stall_by_name(merchant_stalls[0].name)
|
72
|
-
)
|
73
|
-
assert result["status"] == "success"
|
74
|
-
assert result["stall_name"] == merchant_stalls[0].name
|
75
|
-
|
76
|
-
|
77
|
-
def test_publish_products_by_stall_name(
|
78
|
-
merchant_tools: MerchantTools,
|
79
|
-
product_event_ids: List[EventId],
|
80
|
-
merchant_stalls: List[MerchantStall],
|
81
|
-
) -> None:
|
82
|
-
"""Test publishing all products in a stall"""
|
83
|
-
with patch.object(
|
84
|
-
merchant_tools.get_nostr_client(), "publish_product"
|
85
|
-
) as mock_publish:
|
86
|
-
mock_publish.side_effect = itertools.cycle(product_event_ids)
|
87
|
-
|
88
|
-
results = json.loads(
|
89
|
-
merchant_tools.publish_products_by_stall_name(merchant_stalls[0].name)
|
90
|
-
)
|
91
|
-
assert len(results) == 2
|
92
|
-
assert all(r["status"] == "success" for r in results)
|
93
|
-
|
94
|
-
|
95
|
-
def test_publish_all_products(
|
96
|
-
merchant_tools: MerchantTools, product_event_ids: List[EventId]
|
97
|
-
) -> None:
|
98
|
-
"""Test publishing all products"""
|
99
|
-
with patch.object(
|
100
|
-
merchant_tools.get_nostr_client(), "publish_product"
|
101
|
-
) as mock_publish:
|
102
|
-
mock_publish.side_effect = itertools.cycle(product_event_ids)
|
103
|
-
|
104
|
-
results = json.loads(merchant_tools.publish_all_products())
|
105
|
-
assert len(results) == 3
|
106
|
-
|
107
|
-
|
108
|
-
def test_publish_all_stalls(
|
109
|
-
merchant_tools: MerchantTools, stall_event_ids: List[EventId]
|
110
|
-
) -> None:
|
111
|
-
"""Test publishing all stalls"""
|
112
|
-
with patch.object(
|
113
|
-
merchant_tools.get_nostr_client(), "publish_stall"
|
114
|
-
) as mock_publish:
|
115
|
-
mock_publish.side_effect = itertools.cycle(stall_event_ids)
|
116
|
-
|
117
|
-
results = json.loads(merchant_tools.publish_all_stalls())
|
118
|
-
assert len(results) == 2
|
119
|
-
|
120
|
-
|
121
|
-
def test_error_handling(merchant_tools: MerchantTools) -> None:
|
122
|
-
"""Test error handling in various scenarios"""
|
123
|
-
result = json.loads(merchant_tools.publish_product_by_name("NonExistentProduct"))
|
124
|
-
assert result["status"] == "error"
|
125
|
-
|
126
|
-
results = json.loads(merchant_tools.publish_stall_by_name("NonExistentStall"))
|
127
|
-
assert isinstance(results, list)
|
128
|
-
assert results[0]["status"] == "error"
|
129
|
-
|
130
|
-
results = json.loads(
|
131
|
-
merchant_tools.publish_products_by_stall_name("NonExistentStall")
|
132
|
-
)
|
133
|
-
assert isinstance(results, list)
|
134
|
-
assert results[0]["status"] == "error"
|
135
|
-
|
136
|
-
|
137
|
-
def test_profile_operations(
|
138
|
-
merchant_tools: MerchantTools,
|
139
|
-
profile_event_id: EventId,
|
140
|
-
merchant_profile_name: str,
|
141
|
-
merchant_profile_about: str,
|
142
|
-
) -> None:
|
143
|
-
"""Test profile-related operations"""
|
144
|
-
profile_data = json.loads(merchant_tools.get_profile())
|
145
|
-
profile = json.loads(profile_data) # Parse the nested JSON string
|
146
|
-
assert profile["name"] == merchant_profile_name
|
147
|
-
assert profile["about"] == merchant_profile_about
|
148
|
-
|
149
|
-
with patch.object(
|
150
|
-
merchant_tools.get_nostr_client(), "publish_profile"
|
151
|
-
) as mock_publish:
|
152
|
-
mock_publish.return_value = profile_event_id
|
153
|
-
result = json.loads(merchant_tools.publish_profile())
|
154
|
-
assert isinstance(result, dict)
|
@@ -1,98 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains tests for the NostrClient class using a real Nostr relay.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from typing import List
|
6
|
-
|
7
|
-
import pytest
|
8
|
-
|
9
|
-
from agentstr.models import MerchantProduct, MerchantStall
|
10
|
-
from agentstr.nostr import EventId, Keys, NostrClient
|
11
|
-
|
12
|
-
|
13
|
-
# used in test_nostr_integration.py
|
14
|
-
@pytest.fixture(scope="session", name="nostr_client")
|
15
|
-
def nostr_client_fixture(relay: str, merchant_keys: Keys) -> NostrClient:
|
16
|
-
"""Fixture providing a NostrClient instance"""
|
17
|
-
nostr_client = NostrClient(relay, merchant_keys.secret_key().to_bech32())
|
18
|
-
return nostr_client
|
19
|
-
|
20
|
-
|
21
|
-
class TestNostrClient:
|
22
|
-
"""Test suite for NostrClient"""
|
23
|
-
|
24
|
-
def test_publish_profile(
|
25
|
-
self,
|
26
|
-
nostr_client: NostrClient,
|
27
|
-
merchant_profile_name: str,
|
28
|
-
merchant_profile_about: str,
|
29
|
-
merchant_profile_picture: str,
|
30
|
-
) -> None:
|
31
|
-
"""Test publishing a profile"""
|
32
|
-
event_id = nostr_client.publish_profile(
|
33
|
-
name=merchant_profile_name,
|
34
|
-
about=merchant_profile_about,
|
35
|
-
picture=merchant_profile_picture,
|
36
|
-
)
|
37
|
-
assert isinstance(event_id, EventId)
|
38
|
-
|
39
|
-
def test_publish_stall(
|
40
|
-
self, nostr_client: NostrClient, merchant_stalls: List[MerchantStall]
|
41
|
-
) -> None:
|
42
|
-
"""Test publishing a stall"""
|
43
|
-
event_id = nostr_client.publish_stall(merchant_stalls[0])
|
44
|
-
assert isinstance(event_id, EventId)
|
45
|
-
|
46
|
-
def test_publish_product(
|
47
|
-
self, nostr_client: NostrClient, merchant_products: List[MerchantProduct]
|
48
|
-
) -> None:
|
49
|
-
"""Test publishing a product"""
|
50
|
-
event_id = nostr_client.publish_product(merchant_products[0])
|
51
|
-
assert isinstance(event_id, EventId)
|
52
|
-
|
53
|
-
# def test_delete_event(
|
54
|
-
# self, nostr_client: NostrClient, test_merchant_stall: MerchantStall
|
55
|
-
# ) -> None:
|
56
|
-
# """Test deleting an event"""
|
57
|
-
# # First publish something to delete
|
58
|
-
# event_id = nostr_client.publish_stall(test_merchant_stall)
|
59
|
-
# assert isinstance(event_id, EventId)
|
60
|
-
|
61
|
-
# # Then delete it
|
62
|
-
# delete_event_id = nostr_client.delete_event(event_id, reason="Test deletion")
|
63
|
-
# assert isinstance(delete_event_id, EventId)
|
64
|
-
|
65
|
-
def test_retrieve_products_from_seller(
|
66
|
-
self, nostr_client: NostrClient, merchant_keys: Keys
|
67
|
-
) -> None:
|
68
|
-
"""Test retrieving products from a seller"""
|
69
|
-
products = nostr_client.retrieve_products_from_seller(
|
70
|
-
merchant_keys.public_key()
|
71
|
-
)
|
72
|
-
assert len(products) > 0
|
73
|
-
for product in products:
|
74
|
-
assert isinstance(product, MerchantProduct)
|
75
|
-
# print(f"Product: {product.name}")
|
76
|
-
|
77
|
-
def test_retrieve_sellers(self, nostr_client: NostrClient) -> None:
|
78
|
-
"""Test retrieving sellers"""
|
79
|
-
try:
|
80
|
-
sellers = nostr_client.retrieve_sellers()
|
81
|
-
assert len(sellers) > 0
|
82
|
-
except RuntimeError as e:
|
83
|
-
# print(f"\nError retrieving sellers: {e}")
|
84
|
-
raise e
|
85
|
-
|
86
|
-
def test_retrieve_stalls_from_seller(
|
87
|
-
self, nostr_client: NostrClient, merchant_keys: Keys
|
88
|
-
) -> None:
|
89
|
-
"""Test retrieving stalls from a seller"""
|
90
|
-
stalls = nostr_client.retrieve_stalls_from_seller(merchant_keys.public_key())
|
91
|
-
assert len(stalls) > 0
|
92
|
-
|
93
|
-
def test_retrieve_profile(
|
94
|
-
self, nostr_client: NostrClient, merchant_keys: Keys
|
95
|
-
) -> None:
|
96
|
-
"""Test async retrieve profile"""
|
97
|
-
profile = nostr_client.retrieve_profile(merchant_keys.public_key())
|
98
|
-
assert profile is not None
|
@@ -1,107 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Module to perform mocked tests on the NostrClient class.
|
3
|
-
Used for regular CI/CD testing without connecting to a real Nostr relay.
|
4
|
-
"""
|
5
|
-
|
6
|
-
from typing import Generator, List
|
7
|
-
from unittest.mock import patch
|
8
|
-
|
9
|
-
import pytest
|
10
|
-
|
11
|
-
from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
|
12
|
-
from agentstr.nostr import EventId, Keys, NostrClient
|
13
|
-
|
14
|
-
|
15
|
-
# used in test_nostr_mocked.py
|
16
|
-
@pytest.fixture(name="nostr_client")
|
17
|
-
def mock_nostr_client( # type: ignore[no-untyped-def]
|
18
|
-
profile_event_id: EventId,
|
19
|
-
stall_event_ids: List[EventId],
|
20
|
-
product_event_ids: List[EventId],
|
21
|
-
merchant_products: List[MerchantProduct],
|
22
|
-
merchant_stalls: List[MerchantStall],
|
23
|
-
merchant_profile: AgentProfile,
|
24
|
-
) -> Generator[NostrClient, None, None]:
|
25
|
-
"""
|
26
|
-
mock NostrClient instance
|
27
|
-
"""
|
28
|
-
with patch("agentstr.nostr.NostrClient") as mock_client:
|
29
|
-
instance = mock_client.return_value
|
30
|
-
# mock_event_id = EventId(
|
31
|
-
# public_key=Keys.generate().public_key(),
|
32
|
-
# created_at=Timestamp.from_secs(1739580690),
|
33
|
-
# kind=Kind(0),
|
34
|
-
# tags=[],
|
35
|
-
# content="mock_content",
|
36
|
-
# )
|
37
|
-
instance.publish_profile.return_value = profile_event_id
|
38
|
-
instance.publish_stall.return_value = stall_event_ids[0]
|
39
|
-
instance.publish_product.return_value = product_event_ids[0]
|
40
|
-
instance.retrieve_products_from_seller.return_value = merchant_products
|
41
|
-
instance.retrieve_sellers.return_value = [merchant_profile]
|
42
|
-
instance.retrieve_stalls_from_seller.return_value = merchant_stalls
|
43
|
-
instance.retrieve_profile.return_value = merchant_profile
|
44
|
-
yield instance
|
45
|
-
|
46
|
-
|
47
|
-
class TestNostrClientMocked:
|
48
|
-
"""Mocked test suite for NostrClient"""
|
49
|
-
|
50
|
-
def test_publish_profile(
|
51
|
-
self,
|
52
|
-
nostr_client: NostrClient,
|
53
|
-
merchant_profile_name: str,
|
54
|
-
merchant_profile_about: str,
|
55
|
-
merchant_profile_picture: str,
|
56
|
-
) -> None:
|
57
|
-
"""Test publishing a profile"""
|
58
|
-
event_id = nostr_client.publish_profile(
|
59
|
-
name=merchant_profile_name,
|
60
|
-
about=merchant_profile_about,
|
61
|
-
picture=merchant_profile_picture,
|
62
|
-
)
|
63
|
-
assert isinstance(event_id, EventId)
|
64
|
-
|
65
|
-
def test_publish_stall(
|
66
|
-
self, nostr_client: NostrClient, merchant_stalls: List[MerchantStall]
|
67
|
-
) -> None:
|
68
|
-
"""Test publishing a stall"""
|
69
|
-
event_id = nostr_client.publish_stall(merchant_stalls[0])
|
70
|
-
assert isinstance(event_id, EventId)
|
71
|
-
|
72
|
-
def test_publish_product(
|
73
|
-
self, nostr_client: NostrClient, merchant_products: List[MerchantProduct]
|
74
|
-
) -> None:
|
75
|
-
"""Test publishing a product"""
|
76
|
-
event_id = nostr_client.publish_product(merchant_products[0])
|
77
|
-
assert isinstance(event_id, EventId)
|
78
|
-
|
79
|
-
def test_retrieve_products_from_seller(
|
80
|
-
self, nostr_client: NostrClient, merchant_keys: Keys
|
81
|
-
) -> None:
|
82
|
-
"""Test retrieving products from a seller"""
|
83
|
-
products = nostr_client.retrieve_products_from_seller(
|
84
|
-
merchant_keys.public_key()
|
85
|
-
)
|
86
|
-
assert len(products) > 0
|
87
|
-
for product in products:
|
88
|
-
assert isinstance(product, MerchantProduct)
|
89
|
-
|
90
|
-
def test_retrieve_sellers(self, nostr_client: NostrClient) -> None:
|
91
|
-
"""Test retrieving sellers"""
|
92
|
-
sellers = nostr_client.retrieve_sellers()
|
93
|
-
assert len(sellers) > 0
|
94
|
-
|
95
|
-
def test_retrieve_stalls_from_seller(
|
96
|
-
self, nostr_client: NostrClient, merchant_keys: Keys
|
97
|
-
) -> None:
|
98
|
-
"""Test retrieving stalls from a seller"""
|
99
|
-
stalls = nostr_client.retrieve_stalls_from_seller(merchant_keys.public_key())
|
100
|
-
assert len(stalls) > 0
|
101
|
-
|
102
|
-
def test_retrieve_profile(
|
103
|
-
self, nostr_client: NostrClient, merchant_keys: Keys
|
104
|
-
) -> None:
|
105
|
-
"""Test async retrieve profile"""
|
106
|
-
profile = nostr_client.retrieve_profile(merchant_keys.public_key())
|
107
|
-
assert profile is not None
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|