offering-protocol 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/PKG-INFO +11 -4
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/README.md +10 -3
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/pyproject.toml +3 -3
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/client.py +47 -8
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/details.py +1 -1
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/__init__.py +2 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/models.py +1 -1
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/validation.py +365 -1
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/client.py +48 -3
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/models.py +14 -7
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/service/service.py +15 -7
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/.gitignore +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/LICENSE +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/__init__.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/__init__.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/agent.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/cache.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/capabilities.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/schema.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/references.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/action-relation.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/action-request.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/action.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/attribute-schema-reference.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/authentication-requirement.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/capability-identifier.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/capability-link.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/collection-search-request.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/collection.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/detail-fields.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/enrollment-protocol.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-capability-source.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-definition-page.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-definition.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-expression.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-operator.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-type.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/filter-unit.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/http-action-target.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/invalid-parameter.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/local-resource-identifier-list.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/local-resource-identifier.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/mcp-endpoint.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/offering-search-request.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/offering-search-response.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/offering.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/openapi-action-target.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/operation-descriptor.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/page-envelope.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/page-limit.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/payment-option.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/payment-protocol.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/price-preview.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/problem-code.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/problem-details.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/protocol-version.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/refinement-bucket.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/refinement-group.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/representation.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/resource-identity.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/resource-image.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/resource-reference.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/schema-reference.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/search-capabilities.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/service-branding-image.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/service-branding.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/service-document.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/service-openapi.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/service-origin.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/service-protocols.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/sort-capability-source.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/sort-definition-page.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/sort-definition.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/sort-key.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/top-level-document.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/schemas/trust-protocol.schema.json +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/__init__.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/transport.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/py.typed +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/service/__init__.py +0 -0
- {offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/service/static_catalog.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: offering-protocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Official Python SDK for the Offering Discovery Protocol
|
|
5
5
|
Project-URL: Documentation, https://www.offeringprotocol.org/
|
|
6
6
|
Project-URL: Issues, https://github.com/offering-protocol/odp-python/issues
|
|
@@ -94,8 +94,9 @@ async def main() -> None:
|
|
|
94
94
|
asyncio.run(main())
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Use `search_services()` when the application wants bounded automatic pagination.
|
|
98
|
-
|
|
97
|
+
Use `search_services()` when the application wants bounded automatic pagination. Search responses
|
|
98
|
+
provide facets for enrollment protocols, keywords, operations, payment protocols, payment options,
|
|
99
|
+
and trust protocols. Use `suggest()` to discover keyword completions supported by the Directory.
|
|
99
100
|
|
|
100
101
|
## Inspect and navigate a Service
|
|
101
102
|
|
|
@@ -266,7 +267,9 @@ service = (
|
|
|
266
267
|
Every Service integration must implement `list-offerings` and `get-offering`. `StaticCatalog` is the
|
|
267
268
|
small-Service implementation: it adds Collection operations when Collections are provided and uses
|
|
268
269
|
integrity-protected, stateless continuations that expire after one hour. Larger Services can
|
|
269
|
-
|
|
270
|
+
subclass the typed `Catalog` protocol over their existing indexed catalog and search infrastructure.
|
|
271
|
+
The protocol supplies rejecting defaults for optional operations; override an optional method and
|
|
272
|
+
include its matching `Operation` only when the Service implements it.
|
|
270
273
|
|
|
271
274
|
Service responses are validated against the bundled normative schemas before they are returned.
|
|
272
275
|
The handler enforces fixed operation paths and methods, ODP media types, request and response byte
|
|
@@ -281,6 +284,10 @@ its Actions can advertise enrollment, payment, and trust protocols, but ODP does
|
|
|
281
284
|
credentials, invoke Actions, submit payments, or implement trust protocols. Applications compose
|
|
282
285
|
the appropriate protocol clients around an Action resolved through ODP.
|
|
283
286
|
|
|
287
|
+
`parse_service_document` is the strict current-version Service parser. Agent inspection and
|
|
288
|
+
Directory results filter unrecognized enrollment, payment, and trust descriptors while retaining
|
|
289
|
+
strict validation for recognized descriptors.
|
|
290
|
+
|
|
284
291
|
## Errors and validation
|
|
285
292
|
|
|
286
293
|
Each role exposes typed errors:
|
|
@@ -65,8 +65,9 @@ async def main() -> None:
|
|
|
65
65
|
asyncio.run(main())
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Use `search_services()` when the application wants bounded automatic pagination.
|
|
69
|
-
|
|
68
|
+
Use `search_services()` when the application wants bounded automatic pagination. Search responses
|
|
69
|
+
provide facets for enrollment protocols, keywords, operations, payment protocols, payment options,
|
|
70
|
+
and trust protocols. Use `suggest()` to discover keyword completions supported by the Directory.
|
|
70
71
|
|
|
71
72
|
## Inspect and navigate a Service
|
|
72
73
|
|
|
@@ -237,7 +238,9 @@ service = (
|
|
|
237
238
|
Every Service integration must implement `list-offerings` and `get-offering`. `StaticCatalog` is the
|
|
238
239
|
small-Service implementation: it adds Collection operations when Collections are provided and uses
|
|
239
240
|
integrity-protected, stateless continuations that expire after one hour. Larger Services can
|
|
240
|
-
|
|
241
|
+
subclass the typed `Catalog` protocol over their existing indexed catalog and search infrastructure.
|
|
242
|
+
The protocol supplies rejecting defaults for optional operations; override an optional method and
|
|
243
|
+
include its matching `Operation` only when the Service implements it.
|
|
241
244
|
|
|
242
245
|
Service responses are validated against the bundled normative schemas before they are returned.
|
|
243
246
|
The handler enforces fixed operation paths and methods, ODP media types, request and response byte
|
|
@@ -252,6 +255,10 @@ its Actions can advertise enrollment, payment, and trust protocols, but ODP does
|
|
|
252
255
|
credentials, invoke Actions, submit payments, or implement trust protocols. Applications compose
|
|
253
256
|
the appropriate protocol clients around an Action resolved through ODP.
|
|
254
257
|
|
|
258
|
+
`parse_service_document` is the strict current-version Service parser. Agent inspection and
|
|
259
|
+
Directory results filter unrecognized enrollment, payment, and trust descriptors while retaining
|
|
260
|
+
strict validation for recognized descriptors.
|
|
261
|
+
|
|
255
262
|
## Errors and validation
|
|
256
263
|
|
|
257
264
|
Each role exposes typed errors:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "offering-protocol"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Official Python SDK for the Offering Discovery Protocol"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -41,12 +41,12 @@ Repository = "https://github.com/offering-protocol/odp-python"
|
|
|
41
41
|
|
|
42
42
|
[dependency-groups]
|
|
43
43
|
dev = [
|
|
44
|
-
"build==1.
|
|
44
|
+
"build==1.6.1",
|
|
45
45
|
"mypy==2.3.1",
|
|
46
46
|
"pytest==9.1.1",
|
|
47
47
|
"pytest-asyncio==1.4.0",
|
|
48
48
|
"pytest-cov==7.1.0",
|
|
49
|
-
"ruff==0.16.
|
|
49
|
+
"ruff==0.16.7",
|
|
50
50
|
"twine==7.0.0",
|
|
51
51
|
"types-jsonschema==4.26.0.20260518",
|
|
52
52
|
]
|
|
@@ -21,18 +21,30 @@ from offering_protocol.core import (
|
|
|
21
21
|
OfferingSearchRequest,
|
|
22
22
|
Operation,
|
|
23
23
|
Page,
|
|
24
|
+
ProblemDetails,
|
|
24
25
|
Representation,
|
|
25
26
|
ServiceDocument,
|
|
26
27
|
build_operation_url,
|
|
27
28
|
derive_service_origin,
|
|
28
|
-
|
|
29
|
-
parse_collection_page,
|
|
30
|
-
parse_offering,
|
|
31
|
-
parse_offering_page,
|
|
32
|
-
parse_problem_response,
|
|
33
|
-
parse_service_document,
|
|
29
|
+
parse_agent_service_document,
|
|
34
30
|
resolve_continuation,
|
|
35
31
|
)
|
|
32
|
+
from offering_protocol.core import (
|
|
33
|
+
parse_collection as parse_collection_strict,
|
|
34
|
+
)
|
|
35
|
+
from offering_protocol.core import (
|
|
36
|
+
parse_collection_page as parse_collection_page_strict,
|
|
37
|
+
)
|
|
38
|
+
from offering_protocol.core import (
|
|
39
|
+
parse_offering as parse_offering_strict,
|
|
40
|
+
)
|
|
41
|
+
from offering_protocol.core import (
|
|
42
|
+
parse_offering_page as parse_offering_page_strict,
|
|
43
|
+
)
|
|
44
|
+
from offering_protocol.core import (
|
|
45
|
+
parse_problem_response as parse_problem_response_strict,
|
|
46
|
+
)
|
|
47
|
+
from offering_protocol.core.validation import _normalize_agent_response
|
|
36
48
|
from offering_protocol.directory.transport import (
|
|
37
49
|
HttpRequest,
|
|
38
50
|
HttpResponse,
|
|
@@ -136,10 +148,10 @@ class ServiceClient:
|
|
|
136
148
|
b"",
|
|
137
149
|
_MAXIMUM_DOCUMENT_BYTES,
|
|
138
150
|
self._cache_fallbacks.service_document,
|
|
139
|
-
|
|
151
|
+
parse_agent_service_document,
|
|
140
152
|
)
|
|
141
153
|
return Inspection(
|
|
142
|
-
document=
|
|
154
|
+
document=parse_agent_service_document(response.body),
|
|
143
155
|
final_url=response.final_url,
|
|
144
156
|
freshness=response.freshness,
|
|
145
157
|
requested_url=requested_url,
|
|
@@ -571,6 +583,33 @@ def _operation_parser(operation: Operation) -> object:
|
|
|
571
583
|
return parse_offering_page
|
|
572
584
|
|
|
573
585
|
|
|
586
|
+
def parse_collection(data: bytes | str) -> Collection:
|
|
587
|
+
return parse_collection_strict(_normalize_body(data, "collection"))
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def parse_offering(data: bytes | str) -> Offering:
|
|
591
|
+
return parse_offering_strict(_normalize_body(data, "offering"))
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def parse_collection_page(data: bytes | str) -> Page[Collection]:
|
|
595
|
+
return parse_collection_page_strict(_normalize_body(data, "collection-page"))
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def parse_offering_page(data: bytes | str) -> OfferingPage[Offering]:
|
|
599
|
+
return parse_offering_page_strict(_normalize_body(data, "offering-page"))
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def parse_problem_response(data: bytes | str, status: int) -> ProblemDetails:
|
|
603
|
+
return parse_problem_response_strict(_normalize_body(data, "problem"), status)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _normalize_body(data: bytes | str, kind: str) -> str:
|
|
607
|
+
raw = json.loads(data)
|
|
608
|
+
if not isinstance(raw, dict):
|
|
609
|
+
return data.decode() if isinstance(data, bytes) else data
|
|
610
|
+
return json.dumps(_normalize_agent_response(raw, kind), separators=(",", ":"))
|
|
611
|
+
|
|
612
|
+
|
|
574
613
|
def _encode(value: object) -> bytes:
|
|
575
614
|
if not hasattr(value, "model_dump_json"):
|
|
576
615
|
raise TypeError("ODP model is not serializable")
|
|
@@ -16,6 +16,7 @@ from offering_protocol.core.references import (
|
|
|
16
16
|
from offering_protocol.core.validation import (
|
|
17
17
|
OdpValidationError,
|
|
18
18
|
ValidationIssue,
|
|
19
|
+
parse_agent_service_document,
|
|
19
20
|
parse_collection,
|
|
20
21
|
parse_collection_page,
|
|
21
22
|
parse_collection_search_request,
|
|
@@ -44,6 +45,7 @@ __all__ = [
|
|
|
44
45
|
"is_local_resource_identifier",
|
|
45
46
|
"operation_method",
|
|
46
47
|
"operation_path",
|
|
48
|
+
"parse_agent_service_document",
|
|
47
49
|
"parse_collection",
|
|
48
50
|
"parse_collection_page",
|
|
49
51
|
"parse_collection_search_request",
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/validation.py
RENAMED
|
@@ -82,6 +82,363 @@ def parse_service_document(data: bytes | str) -> ServiceDocument:
|
|
|
82
82
|
return value
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def parse_agent_service_document(data: bytes | str) -> ServiceDocument:
|
|
86
|
+
try:
|
|
87
|
+
raw = json.loads(data)
|
|
88
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
89
|
+
return parse_service_document(data)
|
|
90
|
+
if isinstance(raw, dict):
|
|
91
|
+
raw = _normalize_agent_response(raw, "service-document")
|
|
92
|
+
return parse_service_document(json.dumps(raw, separators=(",", ":")))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _normalize_agent_response(value: dict[str, Any], kind: str) -> dict[str, Any]:
|
|
96
|
+
normalized = dict(value)
|
|
97
|
+
if kind == "service-document":
|
|
98
|
+
_filter_agent_protocols(normalized)
|
|
99
|
+
protocols = normalized.get("protocols")
|
|
100
|
+
if isinstance(protocols, dict):
|
|
101
|
+
_filter_unknown_authentication(protocols, "payments")
|
|
102
|
+
_filter_named_list(
|
|
103
|
+
normalized,
|
|
104
|
+
"operations",
|
|
105
|
+
{
|
|
106
|
+
"get-collection",
|
|
107
|
+
"get-offering",
|
|
108
|
+
"list-collection-offerings",
|
|
109
|
+
"list-collections",
|
|
110
|
+
"list-offerings",
|
|
111
|
+
"search-collections",
|
|
112
|
+
"search-offerings",
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
_filter_unknown_authentication(normalized, "operations")
|
|
116
|
+
_filter_typed_list(normalized, "mcp", {"streamable-http"})
|
|
117
|
+
_filter_closed_object_list(normalized, "operations", {"authentication", "name"})
|
|
118
|
+
_filter_closed_object_list(normalized, "mcp", {"description", "name", "type", "url"})
|
|
119
|
+
_filter_payment_options(normalized)
|
|
120
|
+
_normalize_branding(normalized)
|
|
121
|
+
_normalize_search_capabilities(normalized)
|
|
122
|
+
elif kind in {"collection", "offering"}:
|
|
123
|
+
_filter_typed_list(
|
|
124
|
+
normalized,
|
|
125
|
+
"images",
|
|
126
|
+
{"image/avif", "image/jpeg", "image/png", "image/svg+xml", "image/webp"},
|
|
127
|
+
)
|
|
128
|
+
_strip_object_list(normalized, "images", {"alt", "height", "src", "type", "width"})
|
|
129
|
+
_normalize_search_capabilities(normalized)
|
|
130
|
+
if kind == "offering":
|
|
131
|
+
_normalize_offering(normalized)
|
|
132
|
+
elif kind in {"collection-page", "offering-page"} and isinstance(normalized.get("items"), list):
|
|
133
|
+
item_kind = "offering" if kind == "offering-page" else "collection"
|
|
134
|
+
normalized["items"] = [
|
|
135
|
+
_normalize_agent_response(item, item_kind) if isinstance(item, dict) else item
|
|
136
|
+
for item in normalized["items"]
|
|
137
|
+
]
|
|
138
|
+
elif kind in {"filter-page", "sort-page"} and isinstance(normalized.get("items"), list):
|
|
139
|
+
predicate = _known_filter if kind == "filter-page" else _known_sort
|
|
140
|
+
normalized["items"] = [item for item in normalized["items"] if predicate(item)]
|
|
141
|
+
elif kind == "problem" and isinstance(normalized.get("invalid_params"), list):
|
|
142
|
+
normalized["invalid_params"] = [
|
|
143
|
+
item
|
|
144
|
+
for item in normalized["invalid_params"]
|
|
145
|
+
if not (
|
|
146
|
+
isinstance(item, dict)
|
|
147
|
+
and isinstance(item.get("in"), str)
|
|
148
|
+
and item["in"] not in {"body", "header", "path", "query"}
|
|
149
|
+
)
|
|
150
|
+
]
|
|
151
|
+
return normalized
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _filter_named_list(value: dict[str, Any], member: str, recognized: set[str]) -> None:
|
|
155
|
+
items = value.get(member)
|
|
156
|
+
if not isinstance(items, list):
|
|
157
|
+
return
|
|
158
|
+
filtered = [
|
|
159
|
+
item
|
|
160
|
+
for item in items
|
|
161
|
+
if not (
|
|
162
|
+
isinstance(item, dict)
|
|
163
|
+
and isinstance(item.get("name"), str)
|
|
164
|
+
and item["name"] not in recognized
|
|
165
|
+
)
|
|
166
|
+
]
|
|
167
|
+
if filtered:
|
|
168
|
+
value[member] = filtered
|
|
169
|
+
else:
|
|
170
|
+
value.pop(member, None)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _filter_typed_list(value: dict[str, Any], member: str, recognized: set[str]) -> None:
|
|
174
|
+
items = value.get(member)
|
|
175
|
+
if not isinstance(items, list):
|
|
176
|
+
return
|
|
177
|
+
filtered = [
|
|
178
|
+
item
|
|
179
|
+
for item in items
|
|
180
|
+
if not (
|
|
181
|
+
isinstance(item, dict)
|
|
182
|
+
and isinstance(item.get("type"), str)
|
|
183
|
+
and item["type"] not in recognized
|
|
184
|
+
)
|
|
185
|
+
]
|
|
186
|
+
if filtered:
|
|
187
|
+
value[member] = filtered
|
|
188
|
+
else:
|
|
189
|
+
value.pop(member, None)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _filter_closed_object_list(value: dict[str, Any], member: str, allowed: set[str]) -> None:
|
|
193
|
+
items = value.get(member)
|
|
194
|
+
if not isinstance(items, list):
|
|
195
|
+
return
|
|
196
|
+
filtered = [
|
|
197
|
+
item for item in items if not isinstance(item, dict) or not set(item).difference(allowed)
|
|
198
|
+
]
|
|
199
|
+
if filtered:
|
|
200
|
+
value[member] = filtered
|
|
201
|
+
else:
|
|
202
|
+
value.pop(member, None)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _filter_unknown_authentication(value: dict[str, Any], member: str) -> None:
|
|
206
|
+
items = value.get(member)
|
|
207
|
+
if not isinstance(items, list):
|
|
208
|
+
return
|
|
209
|
+
filtered = [
|
|
210
|
+
item
|
|
211
|
+
for item in items
|
|
212
|
+
if not isinstance(item, dict) or not _has_unknown_authentication(item)
|
|
213
|
+
]
|
|
214
|
+
if filtered:
|
|
215
|
+
value[member] = filtered
|
|
216
|
+
else:
|
|
217
|
+
value.pop(member, None)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _has_unknown_authentication(value: dict[str, Any]) -> bool:
|
|
221
|
+
authentication = value.get("authentication")
|
|
222
|
+
return isinstance(authentication, str) and authentication not in {
|
|
223
|
+
"not-required",
|
|
224
|
+
"optional",
|
|
225
|
+
"required",
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _strip_object_list(value: dict[str, Any], member: str, allowed: set[str]) -> None:
|
|
230
|
+
items = value.get(member)
|
|
231
|
+
if not isinstance(items, list):
|
|
232
|
+
return
|
|
233
|
+
value[member] = [
|
|
234
|
+
{key: entry for key, entry in item.items() if key in allowed}
|
|
235
|
+
if isinstance(item, dict)
|
|
236
|
+
else item
|
|
237
|
+
for item in items
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _filter_payment_options(value: dict[str, Any]) -> None:
|
|
242
|
+
protocols = value.get("protocols")
|
|
243
|
+
if not isinstance(protocols, dict) or not isinstance(protocols.get("payments"), list):
|
|
244
|
+
return
|
|
245
|
+
recognized = {
|
|
246
|
+
"algorand",
|
|
247
|
+
"aptos",
|
|
248
|
+
"arbitrum",
|
|
249
|
+
"avalanche",
|
|
250
|
+
"base",
|
|
251
|
+
"card",
|
|
252
|
+
"ethereum",
|
|
253
|
+
"hedera",
|
|
254
|
+
"inflow",
|
|
255
|
+
"lightning",
|
|
256
|
+
"polygon",
|
|
257
|
+
"solana",
|
|
258
|
+
"stellar",
|
|
259
|
+
"stripe",
|
|
260
|
+
"tempo",
|
|
261
|
+
"ton",
|
|
262
|
+
}
|
|
263
|
+
for payment in protocols["payments"]:
|
|
264
|
+
if not isinstance(payment, dict) or not isinstance(payment.get("options"), list):
|
|
265
|
+
continue
|
|
266
|
+
options = [
|
|
267
|
+
option
|
|
268
|
+
for option in payment["options"]
|
|
269
|
+
if not isinstance(option, str) or option in recognized
|
|
270
|
+
]
|
|
271
|
+
if options:
|
|
272
|
+
payment["options"] = options
|
|
273
|
+
else:
|
|
274
|
+
payment.pop("options", None)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _normalize_branding(value: dict[str, Any]) -> None:
|
|
278
|
+
branding = value.get("branding")
|
|
279
|
+
if not isinstance(branding, dict):
|
|
280
|
+
return
|
|
281
|
+
normalized = {key: entry for key, entry in branding.items() if key in {"icon", "logo"}}
|
|
282
|
+
for member in ("icon", "logo"):
|
|
283
|
+
image = normalized.get(member)
|
|
284
|
+
if (
|
|
285
|
+
isinstance(image, dict)
|
|
286
|
+
and isinstance(image.get("type"), str)
|
|
287
|
+
and image["type"] not in {"image/png", "image/svg+xml", "image/webp"}
|
|
288
|
+
):
|
|
289
|
+
normalized.pop(member, None)
|
|
290
|
+
elif isinstance(image, dict):
|
|
291
|
+
normalized[member] = {
|
|
292
|
+
key: entry for key, entry in image.items() if key in {"src", "type"}
|
|
293
|
+
}
|
|
294
|
+
if normalized:
|
|
295
|
+
value["branding"] = normalized
|
|
296
|
+
else:
|
|
297
|
+
value.pop("branding", None)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _normalize_search_capabilities(value: dict[str, Any]) -> None:
|
|
301
|
+
capabilities = value.get("search_capabilities")
|
|
302
|
+
if not isinstance(capabilities, dict):
|
|
303
|
+
return
|
|
304
|
+
normalized = dict(capabilities)
|
|
305
|
+
for member, predicate in (("filters", _known_filter), ("sorts", _known_sort)):
|
|
306
|
+
source = normalized.get(member)
|
|
307
|
+
if not isinstance(source, dict) or not isinstance(source.get("inline"), list):
|
|
308
|
+
continue
|
|
309
|
+
items = [item for item in source["inline"] if predicate(item)]
|
|
310
|
+
if items:
|
|
311
|
+
normalized[member] = {**source, "inline": items}
|
|
312
|
+
else:
|
|
313
|
+
normalized.pop(member, None)
|
|
314
|
+
if normalized:
|
|
315
|
+
value["search_capabilities"] = normalized
|
|
316
|
+
else:
|
|
317
|
+
value.pop("search_capabilities", None)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _normalize_offering(value: dict[str, Any]) -> None:
|
|
321
|
+
schema = value.get("schema")
|
|
322
|
+
if isinstance(schema, dict) and set(schema).difference({"url"}):
|
|
323
|
+
value.pop("schema", None)
|
|
324
|
+
price = value.get("price")
|
|
325
|
+
if (
|
|
326
|
+
isinstance(price, dict)
|
|
327
|
+
and isinstance(price.get("type"), str)
|
|
328
|
+
and price["type"] not in {"fixed", "free", "metered", "quote", "range", "starting_at"}
|
|
329
|
+
):
|
|
330
|
+
value.pop("price", None)
|
|
331
|
+
actions = value.get("actions")
|
|
332
|
+
if not isinstance(actions, list):
|
|
333
|
+
return
|
|
334
|
+
filtered = []
|
|
335
|
+
for action in actions:
|
|
336
|
+
if isinstance(action, dict) and _has_unknown_authentication(action):
|
|
337
|
+
continue
|
|
338
|
+
if isinstance(action, dict) and set(action).difference(
|
|
339
|
+
{"authentication", "description", "http", "id", "openapi", "rel"}
|
|
340
|
+
):
|
|
341
|
+
continue
|
|
342
|
+
http = action.get("http") if isinstance(action, dict) else None
|
|
343
|
+
if isinstance(http, dict) and set(http).difference(
|
|
344
|
+
{"href", "method", "request", "response_content_types"}
|
|
345
|
+
):
|
|
346
|
+
continue
|
|
347
|
+
request = http.get("request") if isinstance(http, dict) else None
|
|
348
|
+
if isinstance(request, dict) and set(request).difference({"content_type", "schema"}):
|
|
349
|
+
continue
|
|
350
|
+
schema = request.get("schema") if isinstance(request, dict) else None
|
|
351
|
+
if isinstance(schema, dict) and set(schema).difference({"url"}):
|
|
352
|
+
continue
|
|
353
|
+
openapi = action.get("openapi") if isinstance(action, dict) else None
|
|
354
|
+
if isinstance(openapi, dict) and set(openapi).difference({"operation_id", "url"}):
|
|
355
|
+
continue
|
|
356
|
+
method = http.get("method") if isinstance(http, dict) else None
|
|
357
|
+
if isinstance(method, str) and method not in {"GET", "POST"}:
|
|
358
|
+
continue
|
|
359
|
+
filtered.append(action)
|
|
360
|
+
if filtered:
|
|
361
|
+
value["actions"] = filtered
|
|
362
|
+
else:
|
|
363
|
+
value.pop("actions", None)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _known_filter(value: Any) -> bool:
|
|
367
|
+
if not isinstance(value, dict):
|
|
368
|
+
return True
|
|
369
|
+
if isinstance(value.get("type"), str) and value["type"] not in {
|
|
370
|
+
"boolean",
|
|
371
|
+
"date",
|
|
372
|
+
"date-time",
|
|
373
|
+
"decimal",
|
|
374
|
+
"integer",
|
|
375
|
+
"number",
|
|
376
|
+
"string",
|
|
377
|
+
}:
|
|
378
|
+
return False
|
|
379
|
+
operators = value.get("operators")
|
|
380
|
+
if isinstance(operators, list) and any(
|
|
381
|
+
isinstance(operator, str)
|
|
382
|
+
and operator not in {"eq", "exists", "gt", "gte", "in", "lt", "lte"}
|
|
383
|
+
for operator in operators
|
|
384
|
+
):
|
|
385
|
+
return False
|
|
386
|
+
unit = value.get("unit")
|
|
387
|
+
return not (
|
|
388
|
+
isinstance(unit, dict)
|
|
389
|
+
and isinstance(unit.get("system"), str)
|
|
390
|
+
and unit["system"] not in {"service", "ucum"}
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _known_sort(value: Any) -> bool:
|
|
395
|
+
if not isinstance(value, dict) or not isinstance(value.get("keys"), list):
|
|
396
|
+
return True
|
|
397
|
+
return not any(
|
|
398
|
+
isinstance(key, dict)
|
|
399
|
+
and (
|
|
400
|
+
(
|
|
401
|
+
isinstance(key.get("direction"), str)
|
|
402
|
+
and key["direction"] not in {"ascending", "descending"}
|
|
403
|
+
)
|
|
404
|
+
or (isinstance(key.get("missing"), str) and key["missing"] not in {"first", "last"})
|
|
405
|
+
)
|
|
406
|
+
for key in value["keys"]
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _filter_agent_protocols(document: dict[str, Any]) -> None:
|
|
411
|
+
protocols = document.get("protocols")
|
|
412
|
+
if not isinstance(protocols, dict):
|
|
413
|
+
return
|
|
414
|
+
_filter_agent_protocol_category(protocols, "enrollment", {"aep"})
|
|
415
|
+
_filter_agent_protocol_category(protocols, "payments", {"mpp", "x402"})
|
|
416
|
+
_filter_agent_protocol_category(protocols, "trust", {"tap"})
|
|
417
|
+
if not protocols:
|
|
418
|
+
document.pop("protocols")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _filter_agent_protocol_category(
|
|
422
|
+
protocols: dict[str, Any], category: str, recognized: set[str]
|
|
423
|
+
) -> None:
|
|
424
|
+
descriptors = protocols.get(category)
|
|
425
|
+
if not isinstance(descriptors, list):
|
|
426
|
+
return
|
|
427
|
+
filtered = [
|
|
428
|
+
descriptor
|
|
429
|
+
for descriptor in descriptors
|
|
430
|
+
if not (
|
|
431
|
+
isinstance(descriptor, dict)
|
|
432
|
+
and isinstance(descriptor.get("name"), str)
|
|
433
|
+
and descriptor["name"] not in recognized
|
|
434
|
+
)
|
|
435
|
+
]
|
|
436
|
+
if descriptors and not filtered:
|
|
437
|
+
protocols.pop(category)
|
|
438
|
+
else:
|
|
439
|
+
protocols[category] = filtered
|
|
440
|
+
|
|
441
|
+
|
|
85
442
|
def parse_collection(data: bytes | str) -> Collection:
|
|
86
443
|
value = _parse(data, "collection.schema.json", "Collection", Collection)
|
|
87
444
|
_validate_representation(
|
|
@@ -99,7 +456,14 @@ def parse_offering(data: bytes | str) -> Offering:
|
|
|
99
456
|
|
|
100
457
|
|
|
101
458
|
def parse_problem_details(data: bytes | str) -> ProblemDetails:
|
|
102
|
-
|
|
459
|
+
value = _parse(data, "problem-details.schema.json", "Problem Details", ProblemDetails)
|
|
460
|
+
expected_type = "https://offeringprotocol.org/problems/" + value.code.lower().replace("_", "-")
|
|
461
|
+
if value.problem_type != expected_type:
|
|
462
|
+
raise OdpValidationError(
|
|
463
|
+
"Problem Details",
|
|
464
|
+
[_issue("/type", "problem-type", "must correspond to the problem code")],
|
|
465
|
+
)
|
|
466
|
+
return value
|
|
103
467
|
|
|
104
468
|
|
|
105
469
|
def parse_problem_response(data: bytes | str, http_status: int) -> ProblemDetails:
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/client.py
RENAMED
|
@@ -7,7 +7,11 @@ from urllib.parse import urlencode, urljoin
|
|
|
7
7
|
|
|
8
8
|
from pydantic import ValidationError as ModelValidationError
|
|
9
9
|
|
|
10
|
-
from offering_protocol.core import
|
|
10
|
+
from offering_protocol.core import (
|
|
11
|
+
OdpValidationError,
|
|
12
|
+
derive_service_origin,
|
|
13
|
+
parse_agent_service_document,
|
|
14
|
+
)
|
|
11
15
|
from offering_protocol.directory.models import (
|
|
12
16
|
DirectoryService,
|
|
13
17
|
Environment,
|
|
@@ -159,17 +163,52 @@ class DirectoryClient:
|
|
|
159
163
|
|
|
160
164
|
def _parse_search_page(body: bytes) -> SearchPage:
|
|
161
165
|
try:
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
raw = json.loads(body)
|
|
167
|
+
if isinstance(raw, dict) and isinstance(raw.get("items"), list):
|
|
168
|
+
for item in raw["items"]:
|
|
169
|
+
if isinstance(item, dict) and "protocols" in item:
|
|
170
|
+
_normalize_service_protocols(item)
|
|
171
|
+
page = SearchPage.model_validate(raw)
|
|
172
|
+
except (
|
|
173
|
+
UnicodeDecodeError,
|
|
174
|
+
json.JSONDecodeError,
|
|
175
|
+
ModelValidationError,
|
|
176
|
+
OdpValidationError,
|
|
177
|
+
) as error:
|
|
164
178
|
raise DirectoryError(f"invalid Directory response: {error}") from error
|
|
165
179
|
if len(page.items) > 100:
|
|
166
180
|
raise DirectoryError("Directory search page exceeds 100 Services")
|
|
181
|
+
if page.facets is not None and any(
|
|
182
|
+
facet.value.name.value != "tap" for facet in page.facets.trust
|
|
183
|
+
):
|
|
184
|
+
raise DirectoryError("Directory trust facets are invalid")
|
|
167
185
|
for service in page.items:
|
|
168
186
|
if derive_service_origin(service.service_origin) != service.service_origin:
|
|
169
187
|
raise DirectoryError("Directory Service origin is not canonical")
|
|
170
188
|
return page
|
|
171
189
|
|
|
172
190
|
|
|
191
|
+
def _normalize_service_protocols(item: dict[str, object]) -> None:
|
|
192
|
+
candidate = {
|
|
193
|
+
"description": "Directory protocol validation",
|
|
194
|
+
"http": {"endpoint_base": "/"},
|
|
195
|
+
"language": "en",
|
|
196
|
+
"localizations": ["en"],
|
|
197
|
+
"name": "Directory Service",
|
|
198
|
+
"odp_version": "1.0",
|
|
199
|
+
"operations": [
|
|
200
|
+
{"authentication": "not-required", "name": "get-offering"},
|
|
201
|
+
{"authentication": "not-required", "name": "list-offerings"},
|
|
202
|
+
],
|
|
203
|
+
"protocols": item["protocols"],
|
|
204
|
+
}
|
|
205
|
+
document = parse_agent_service_document(json.dumps(candidate, separators=(",", ":")))
|
|
206
|
+
if document.protocols is None:
|
|
207
|
+
item.pop("protocols")
|
|
208
|
+
else:
|
|
209
|
+
item["protocols"] = document.protocols.model_dump(mode="json", exclude_defaults=True)
|
|
210
|
+
|
|
211
|
+
|
|
173
212
|
def _validate_search_request(request: SearchRequest) -> None:
|
|
174
213
|
if request.limit > 100:
|
|
175
214
|
raise DirectoryError("limit must be from 1 through 100")
|
|
@@ -182,6 +221,12 @@ def _validate_search_request(request: SearchRequest) -> None:
|
|
|
182
221
|
or any(not keyword or len(keyword) > 64 for keyword in request.filters.keywords)
|
|
183
222
|
):
|
|
184
223
|
raise DirectoryError("keywords must contain at most 32 values of at most 64 characters")
|
|
224
|
+
if (
|
|
225
|
+
request.filters is not None
|
|
226
|
+
and "trust" in request.filters.model_fields_set
|
|
227
|
+
and (len(request.filters.trust) != 1 or request.filters.trust[0].name.value != "tap")
|
|
228
|
+
):
|
|
229
|
+
raise DirectoryError("trust must contain exactly one tap descriptor")
|
|
185
230
|
|
|
186
231
|
|
|
187
232
|
def _consume_response(response: HttpResponse) -> HttpResponse:
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/models.py
RENAMED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from enum import StrEnum
|
|
6
|
+
from typing import Generic, TypeVar
|
|
6
7
|
|
|
7
8
|
from pydantic import Field
|
|
8
9
|
|
|
@@ -13,10 +14,14 @@ from offering_protocol.core.models import (
|
|
|
13
14
|
Operation,
|
|
14
15
|
OperationDescriptor,
|
|
15
16
|
PaymentOption,
|
|
17
|
+
PaymentProtocol,
|
|
16
18
|
Protocol,
|
|
17
19
|
ServiceProtocols,
|
|
20
|
+
TrustProtocol,
|
|
18
21
|
)
|
|
19
22
|
|
|
23
|
+
FacetValue = TypeVar("FacetValue")
|
|
24
|
+
|
|
20
25
|
|
|
21
26
|
class Environment(StrEnum):
|
|
22
27
|
PRODUCTION = "production"
|
|
@@ -45,6 +50,7 @@ class ServiceFilters(OdpModel):
|
|
|
45
50
|
keywords: list[str] = Field(default_factory=list)
|
|
46
51
|
operations: list[OperationFilter] = Field(default_factory=list)
|
|
47
52
|
payments: list[PaymentFilter] = Field(default_factory=list)
|
|
53
|
+
trust: list[TrustProtocol] = Field(default_factory=list)
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
class SearchRequest(OdpModel):
|
|
@@ -74,17 +80,18 @@ class PaymentOptionFacetValue(OdpModel):
|
|
|
74
80
|
option: PaymentOption
|
|
75
81
|
|
|
76
82
|
|
|
77
|
-
class Facet(OdpModel):
|
|
83
|
+
class Facet(OdpModel, Generic[FacetValue]):
|
|
78
84
|
count: int
|
|
79
|
-
value:
|
|
85
|
+
value: FacetValue
|
|
80
86
|
|
|
81
87
|
|
|
82
88
|
class Facets(OdpModel):
|
|
83
|
-
enrollment: list[Facet] = Field(default_factory=list)
|
|
84
|
-
keywords: list[Facet] = Field(default_factory=list)
|
|
85
|
-
operations: list[Facet] = Field(default_factory=list)
|
|
86
|
-
payment_options: list[Facet] = Field(default_factory=list)
|
|
87
|
-
payments: list[Facet] = Field(default_factory=list)
|
|
89
|
+
enrollment: list[Facet[EnrollmentProtocol]] = Field(default_factory=list)
|
|
90
|
+
keywords: list[Facet[str]] = Field(default_factory=list)
|
|
91
|
+
operations: list[Facet[OperationDescriptor]] = Field(default_factory=list)
|
|
92
|
+
payment_options: list[Facet[PaymentOptionFacetValue]] = Field(default_factory=list)
|
|
93
|
+
payments: list[Facet[PaymentProtocol]] = Field(default_factory=list)
|
|
94
|
+
trust: list[Facet[TrustProtocol]] = Field(default_factory=list)
|
|
88
95
|
|
|
89
96
|
|
|
90
97
|
class SearchPage(OdpModel):
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/service/service.py
RENAMED
|
@@ -98,21 +98,29 @@ class Catalog(Protocol):
|
|
|
98
98
|
|
|
99
99
|
async def search_offerings(
|
|
100
100
|
self, query: OfferingSearchRequest, request: CatalogRequest
|
|
101
|
-
) -> OfferingPage[Offering]:
|
|
101
|
+
) -> OfferingPage[Offering]:
|
|
102
|
+
del query, request
|
|
103
|
+
raise CatalogError("Offering search is not supported")
|
|
102
104
|
|
|
103
|
-
async def list_collections(self, request: CatalogRequest) -> Page[Collection]:
|
|
105
|
+
async def list_collections(self, request: CatalogRequest) -> Page[Collection]:
|
|
106
|
+
del request
|
|
107
|
+
raise CatalogError("Collection listing is not supported")
|
|
104
108
|
|
|
105
|
-
async def get_collection(
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
async def get_collection(self, identifier: str, request: CatalogRequest) -> Collection | None:
|
|
110
|
+
del identifier, request
|
|
111
|
+
raise CatalogError("Collection retrieval is not supported")
|
|
108
112
|
|
|
109
113
|
async def search_collections(
|
|
110
114
|
self, query: CollectionSearchRequest, request: CatalogRequest
|
|
111
|
-
) -> Page[Collection]:
|
|
115
|
+
) -> Page[Collection]:
|
|
116
|
+
del query, request
|
|
117
|
+
raise CatalogError("Collection search is not supported")
|
|
112
118
|
|
|
113
119
|
async def list_collection_offerings(
|
|
114
120
|
self, collection_id: str, request: CatalogRequest
|
|
115
|
-
) -> OfferingPage[Offering]:
|
|
121
|
+
) -> OfferingPage[Offering]:
|
|
122
|
+
del collection_id, request
|
|
123
|
+
raise CatalogError("Collection Offering listing is not supported")
|
|
116
124
|
|
|
117
125
|
|
|
118
126
|
class ServiceBuilder:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/agent/capabilities.py
RENAMED
|
File without changes
|
|
File without changes
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/core/references.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/__init__.py
RENAMED
|
File without changes
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/directory/transport.py
RENAMED
|
File without changes
|
|
File without changes
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/service/__init__.py
RENAMED
|
File without changes
|
{offering_protocol-0.1.0 → offering_protocol-0.2.0}/src/offering_protocol/service/static_catalog.py
RENAMED
|
File without changes
|