spatialdds 0.2.0__tar.gz → 0.3.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.
Files changed (147) hide show
  1. {spatialdds-0.2.0 → spatialdds-0.3.0}/PKG-INFO +62 -7
  2. {spatialdds-0.2.0 → spatialdds-0.3.0}/README.md +61 -6
  3. {spatialdds-0.2.0 → spatialdds-0.3.0}/pyproject.toml +1 -1
  4. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/__init__.py +7 -5
  5. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/client.py +41 -3
  6. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/records.py +228 -35
  7. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds.egg-info/PKG-INFO +62 -7
  8. {spatialdds-0.2.0 → spatialdds-0.3.0}/setup.cfg +0 -0
  9. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/idl/core.idl +0 -0
  10. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/idl/discovery.idl +0 -0
  11. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/idl/types.idl +0 -0
  12. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
  13. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
  14. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
  15. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
  16. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
  17. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
  18. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
  19. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
  20. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
  21. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
  22. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
  23. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
  24. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
  25. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
  26. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
  27. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
  28. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
  29. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
  30. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
  31. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
  32. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
  33. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
  34. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
  35. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
  36. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
  37. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
  38. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
  39. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
  40. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
  41. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
  42. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
  43. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
  44. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
  45. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
  46. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
  47. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
  48. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
  49. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
  50. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
  51. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
  52. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
  53. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
  54. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
  55. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
  56. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
  57. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
  58. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
  59. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
  60. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
  61. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
  62. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
  63. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
  64. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
  65. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
  66. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
  67. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
  68. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
  69. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
  70. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
  71. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
  72. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
  73. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
  74. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
  75. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
  76. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
  77. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
  78. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
  79. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
  80. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
  81. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
  82. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
  83. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
  84. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
  85. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
  86. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
  87. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
  88. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
  89. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
  90. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
  91. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
  92. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
  93. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
  94. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
  95. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
  96. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
  97. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
  98. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
  99. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
  100. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
  101. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
  102. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
  103. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
  104. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
  105. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
  106. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
  107. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
  108. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
  109. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
  110. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
  111. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
  112. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
  113. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
  114. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
  115. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
  116. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
  117. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
  118. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
  119. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
  120. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
  121. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
  122. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
  123. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
  124. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/artifacts/serializer_tables.json +0 -0
  125. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/canonical.py +0 -0
  126. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/canonjson.py +0 -0
  127. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/demo/assets/duck.gltf +0 -0
  128. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/demo/assets/fountain-label.txt +0 -0
  129. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
  130. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/demo/fountain-anchors.mcap +0 -0
  131. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
  132. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
  133. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/errors.py +0 -0
  134. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/serve.py +0 -0
  135. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/__init__.py +0 -0
  136. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/gateway.py +0 -0
  137. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/gw_http.py +0 -0
  138. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/idlmodel.py +0 -0
  139. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/pin.py +0 -0
  140. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/publish.py +0 -0
  141. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/webjson.py +0 -0
  142. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds/server/xcdr2.py +0 -0
  143. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds.egg-info/SOURCES.txt +0 -0
  144. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds.egg-info/dependency_links.txt +0 -0
  145. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds.egg-info/entry_points.txt +0 -0
  146. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds.egg-info/requires.txt +0 -0
  147. {spatialdds-0.2.0 → spatialdds-0.3.0}/spatialdds.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdds
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: SpatialDDS over plain HTTPS and a WebSocket: a client for the web binding (Appendix N), and the reference registry behind one command.
5
5
  Author: Open AR Cloud
6
6
  License: Apache-2.0
@@ -89,17 +89,62 @@ ep.publish_anchor("anchor/duck-perch", lat=30.2849, lon=-97.7393,
89
89
  first, then the record that points at it -- and return what `publish` returns,
90
90
  with the manifest's own result on `.manifest`.
91
91
 
92
+ A full service contract is the same one call, with the claims stated:
93
+
94
+ ```python
95
+ area = sd.circle(30.2849, -97.7393, 500)
96
+ ep.publish_service(
97
+ service_id="mytown-vps", kind="VPS", coverage=area,
98
+ caps=sd.capabilities(
99
+ supported=[sd.profile("spatial.core", minor=8)],
100
+ features=["web-binding/json"]),
101
+ manifest=sd.service_manifest(
102
+ "mytown-vps", "VPS", authority=ep.authority, scene="mytown",
103
+ profiles=["Core", "AR+Geo"],
104
+ request={{"features_topic": "feat.keyframe"}},
105
+ response={{"minimal": "geo.fix"}},
106
+ limits={{"max_fps": 10}},
107
+ auth={{"scheme": "bearer", "issuer": "https://registry.mytown.example"}},
108
+ coverage=area, cells=sd.cells(area)))
109
+ ```
110
+
111
+ Every one of those is something a service operator knows and a library cannot
112
+ guess. [docs/MYTOWN.md](../../docs/MYTOWN.md) publishes that and prints the
113
+ whole served document; `gates/builders.py` rebuilds this repository's
114
+ hand-authored `austin-vps` and duck records through these calls and compares
115
+ them member by member, which is what makes "every member is reachable from an
116
+ argument" a check rather than a claim.
117
+
118
+ **Two forms for an asset, because there are two situations.**
119
+ `sd.asset(path_or_bytes, media_type=...)` reads the data and computes the hash
120
+ and the byte count itself: a hash nobody typed is a hash a reader can trust.
121
+ `sd.asset_ref(url, hash=..., media_type=...)` points at something somebody
122
+ else hosts, and the hash comes from whoever has the bytes. Pass no hash and
123
+ the reference carries no integrity claim, which costs a reader one thing:
124
+ `fetch_asset()` raises rather than reporting a check it could not perform
125
+ (finding 31). `publish_content` takes either, or a path, or bytes, or a URL.
126
+
92
127
  The builders are also usable on their own: `sd.service_announce`,
93
- `sd.content_announce`, `sd.content_manifest`, `sd.geo_anchor`, with
94
- `sd.circle`, `sd.bbox`, `sd.geopose`, `sd.cov_pos3`, `sd.cov_none`,
128
+ `sd.service_manifest`, `sd.content_announce`, `sd.content_manifest`,
129
+ `sd.geo_anchor`, with `sd.circle`, `sd.bbox`, `sd.geopose`, `sd.cov_pos3`,
130
+ `sd.cov_none`, `sd.profile`, `sd.capabilities`, `sd.asset_ref`,
95
131
  `sd.frame`,
96
132
  `sd.earth_fixed`, `sd.stamp`, `sd.topic`, `sd.asset`, `sd.cells`,
97
133
  `sd.service_uri` and `sd.content_uri`. Each returns a plain dict in canonical
98
134
  form, publishable as it stands and dumpable as the authoring JSON this
99
135
  repository's tools read. `spatialdds.records` is the module they live in.
100
136
 
101
- **What they fill in, and what they refuse to.** Three conventions, each
102
- documented where it is applied: a stamp of now unless you pass `when=`, an
137
+ **Conventions fill plumbing. They never fill claims.** A stamp of now, an
138
+ earth-fixed frame, a checksum computed from the record itself: plumbing, with
139
+ one sensible value each, and filling them saves you writing out what you had
140
+ no choice about. A profile list, a rate limit, a request topic, an auth
141
+ scheme, a title: claims, and no default for any of them is honest. State
142
+ nothing and you publish a minimal record, which is *correct*; state a
143
+ contract and you publish it in one call. There is nothing in between for a
144
+ library to invent.
145
+
146
+ **What they fill in, and what they refuse to.** The conventions above are
147
+ documented where they are applied: a stamp of now unless you pass `when=`, an
103
148
  earth-fixed frame unless you pass one, and URIs assembled under a stated
104
149
  authority -- never a guessed one. `ep.publish_*` gets the authority from the
105
150
  endpoint itself (`ep.authority`, which asks the resolver metadata, then the
@@ -160,7 +205,7 @@ the latched set is complete.
160
205
  ```python
161
206
  import spatialdds as sd
162
207
  ep = sd.connect("http://127.0.0.1:8787")
163
- svc = ep.search(geohash="9v6krc")[0]
208
+ svc = next(h for h in ep.search(geohash="9v6krc") if h.serves("geo_anchor"))
164
209
  for ev in svc.topic("geo_anchor").subscribe(timeout=10):
165
210
  if isinstance(ev, sd.EndOfReplay): print("latched set complete:", ev.replayed); break
166
211
  print(" ", ev.key, ev.data["method"], ev.data["geopose"]["stamp"]["sec"])
@@ -227,6 +272,7 @@ call goes away and the bar is one line.
227
272
  | `ep.publish_anchor(anchor_id, lat=, lon=, method=, content=None, ...)` | build and PUT an anchor, optionally naming content |
228
273
  | `ep.withdraw(topic=, key=)` | DELETE: leaves the latched set and disposes |
229
274
  | `hit.service_id`, `.kind`, `.coverage`, `.topics` | the typed service manifest |
275
+ | `hit.serves(type_name)` | does this service offer a topic of that type? for picking one out of a search result |
230
276
  | `hit.topic(type_name)` | a `Topic`, URL taken from the manifest, never built |
231
277
  | `hit.reaches(lat, lon, radius_m)` | does the declared coverage reach a point |
232
278
  | `topic.type`, `.url`, `.name`, `.durability`, `.access`, `.fqn` | what the manifest said |
@@ -236,6 +282,9 @@ call goes away and the bar is one line.
236
282
  | `resolved.doc`, `.uri`, `.etag`, `.assets` | the resolved manifest |
237
283
  | `resolved.asset_url(i)`, `.fetch_asset(i)` | the URL it would fetch, and the verified bytes |
238
284
  | `written.doc`, `.etag`, `.created`, `.manifest` | what a publish returned |
285
+ | `sd.service_manifest(service_id, kind, authority=, scene=, profiles=, request=, response=, limits=, auth=, coverage=, cells=)` | the contract behind an announce; every member a claim, so every member an argument |
286
+ | `sd.capabilities(supported=, preferred=, features=)`, `sd.profile(name, minor=)` | what a service supports, for `service_announce(caps=)` |
287
+ | `sd.asset(path_or_bytes, media_type=)` / `sd.asset_ref(url, hash=, media_type=)` | an asset hashed from its bytes, or a reference to one hosted elsewhere |
239
288
  | `sd.records` | the builders, listed above |
240
289
  | `sd.lift(fqn, doc)` | the lift on its own, for JSON you already have |
241
290
  | `sd.SPEC_COMMIT`, `sd.APPENDIX_N`, `sd.__version__` | what this speaks |
@@ -281,12 +330,18 @@ Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
281
330
  the bytes. Opting out exists and is loud: `verify_integrity=False`, at the call
282
331
  site, nowhere else.
283
332
 
284
- ## Not in 0.2.0
333
+ ## Not in 0.3.0
285
334
 
286
335
  Stated so nobody waits for them: no async API, no caching beyond ETag
287
336
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
288
337
  writes, and no DDS dependency of any kind. The wire is HTTP and a WebSocket.
289
338
 
339
+ `sd.asset` changed meaning in 0.3.0, which is why the version moved: it now
340
+ reads bytes and computes a hash, where in 0.2.0 it took a URL and a hash you
341
+ supplied. That second form is `sd.asset_ref`. A published release has to stay
342
+ reproducible from a public commit, so the API could not change under the same
343
+ number.
344
+
290
345
  On the registry, four things are deferred rather than missing, and the same
291
346
  four are on the 1.9 candidates list: **leases** (nothing expires; `ttl_sec` is
292
347
  advice to caches), **per-provider scopes** (one token, one operator),
@@ -65,17 +65,62 @@ ep.publish_anchor("anchor/duck-perch", lat=30.2849, lon=-97.7393,
65
65
  first, then the record that points at it -- and return what `publish` returns,
66
66
  with the manifest's own result on `.manifest`.
67
67
 
68
+ A full service contract is the same one call, with the claims stated:
69
+
70
+ ```python
71
+ area = sd.circle(30.2849, -97.7393, 500)
72
+ ep.publish_service(
73
+ service_id="mytown-vps", kind="VPS", coverage=area,
74
+ caps=sd.capabilities(
75
+ supported=[sd.profile("spatial.core", minor=8)],
76
+ features=["web-binding/json"]),
77
+ manifest=sd.service_manifest(
78
+ "mytown-vps", "VPS", authority=ep.authority, scene="mytown",
79
+ profiles=["Core", "AR+Geo"],
80
+ request={{"features_topic": "feat.keyframe"}},
81
+ response={{"minimal": "geo.fix"}},
82
+ limits={{"max_fps": 10}},
83
+ auth={{"scheme": "bearer", "issuer": "https://registry.mytown.example"}},
84
+ coverage=area, cells=sd.cells(area)))
85
+ ```
86
+
87
+ Every one of those is something a service operator knows and a library cannot
88
+ guess. [docs/MYTOWN.md](../../docs/MYTOWN.md) publishes that and prints the
89
+ whole served document; `gates/builders.py` rebuilds this repository's
90
+ hand-authored `austin-vps` and duck records through these calls and compares
91
+ them member by member, which is what makes "every member is reachable from an
92
+ argument" a check rather than a claim.
93
+
94
+ **Two forms for an asset, because there are two situations.**
95
+ `sd.asset(path_or_bytes, media_type=...)` reads the data and computes the hash
96
+ and the byte count itself: a hash nobody typed is a hash a reader can trust.
97
+ `sd.asset_ref(url, hash=..., media_type=...)` points at something somebody
98
+ else hosts, and the hash comes from whoever has the bytes. Pass no hash and
99
+ the reference carries no integrity claim, which costs a reader one thing:
100
+ `fetch_asset()` raises rather than reporting a check it could not perform
101
+ (finding 31). `publish_content` takes either, or a path, or bytes, or a URL.
102
+
68
103
  The builders are also usable on their own: `sd.service_announce`,
69
- `sd.content_announce`, `sd.content_manifest`, `sd.geo_anchor`, with
70
- `sd.circle`, `sd.bbox`, `sd.geopose`, `sd.cov_pos3`, `sd.cov_none`,
104
+ `sd.service_manifest`, `sd.content_announce`, `sd.content_manifest`,
105
+ `sd.geo_anchor`, with `sd.circle`, `sd.bbox`, `sd.geopose`, `sd.cov_pos3`,
106
+ `sd.cov_none`, `sd.profile`, `sd.capabilities`, `sd.asset_ref`,
71
107
  `sd.frame`,
72
108
  `sd.earth_fixed`, `sd.stamp`, `sd.topic`, `sd.asset`, `sd.cells`,
73
109
  `sd.service_uri` and `sd.content_uri`. Each returns a plain dict in canonical
74
110
  form, publishable as it stands and dumpable as the authoring JSON this
75
111
  repository's tools read. `spatialdds.records` is the module they live in.
76
112
 
77
- **What they fill in, and what they refuse to.** Three conventions, each
78
- documented where it is applied: a stamp of now unless you pass `when=`, an
113
+ **Conventions fill plumbing. They never fill claims.** A stamp of now, an
114
+ earth-fixed frame, a checksum computed from the record itself: plumbing, with
115
+ one sensible value each, and filling them saves you writing out what you had
116
+ no choice about. A profile list, a rate limit, a request topic, an auth
117
+ scheme, a title: claims, and no default for any of them is honest. State
118
+ nothing and you publish a minimal record, which is *correct*; state a
119
+ contract and you publish it in one call. There is nothing in between for a
120
+ library to invent.
121
+
122
+ **What they fill in, and what they refuse to.** The conventions above are
123
+ documented where they are applied: a stamp of now unless you pass `when=`, an
79
124
  earth-fixed frame unless you pass one, and URIs assembled under a stated
80
125
  authority -- never a guessed one. `ep.publish_*` gets the authority from the
81
126
  endpoint itself (`ep.authority`, which asks the resolver metadata, then the
@@ -136,7 +181,7 @@ the latched set is complete.
136
181
  ```python
137
182
  import spatialdds as sd
138
183
  ep = sd.connect("http://127.0.0.1:8787")
139
- svc = ep.search(geohash="9v6krc")[0]
184
+ svc = next(h for h in ep.search(geohash="9v6krc") if h.serves("geo_anchor"))
140
185
  for ev in svc.topic("geo_anchor").subscribe(timeout=10):
141
186
  if isinstance(ev, sd.EndOfReplay): print("latched set complete:", ev.replayed); break
142
187
  print(" ", ev.key, ev.data["method"], ev.data["geopose"]["stamp"]["sec"])
@@ -203,6 +248,7 @@ call goes away and the bar is one line.
203
248
  | `ep.publish_anchor(anchor_id, lat=, lon=, method=, content=None, ...)` | build and PUT an anchor, optionally naming content |
204
249
  | `ep.withdraw(topic=, key=)` | DELETE: leaves the latched set and disposes |
205
250
  | `hit.service_id`, `.kind`, `.coverage`, `.topics` | the typed service manifest |
251
+ | `hit.serves(type_name)` | does this service offer a topic of that type? for picking one out of a search result |
206
252
  | `hit.topic(type_name)` | a `Topic`, URL taken from the manifest, never built |
207
253
  | `hit.reaches(lat, lon, radius_m)` | does the declared coverage reach a point |
208
254
  | `topic.type`, `.url`, `.name`, `.durability`, `.access`, `.fqn` | what the manifest said |
@@ -212,6 +258,9 @@ call goes away and the bar is one line.
212
258
  | `resolved.doc`, `.uri`, `.etag`, `.assets` | the resolved manifest |
213
259
  | `resolved.asset_url(i)`, `.fetch_asset(i)` | the URL it would fetch, and the verified bytes |
214
260
  | `written.doc`, `.etag`, `.created`, `.manifest` | what a publish returned |
261
+ | `sd.service_manifest(service_id, kind, authority=, scene=, profiles=, request=, response=, limits=, auth=, coverage=, cells=)` | the contract behind an announce; every member a claim, so every member an argument |
262
+ | `sd.capabilities(supported=, preferred=, features=)`, `sd.profile(name, minor=)` | what a service supports, for `service_announce(caps=)` |
263
+ | `sd.asset(path_or_bytes, media_type=)` / `sd.asset_ref(url, hash=, media_type=)` | an asset hashed from its bytes, or a reference to one hosted elsewhere |
215
264
  | `sd.records` | the builders, listed above |
216
265
  | `sd.lift(fqn, doc)` | the lift on its own, for JSON you already have |
217
266
  | `sd.SPEC_COMMIT`, `sd.APPENDIX_N`, `sd.__version__` | what this speaks |
@@ -257,12 +306,18 @@ Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
257
306
  the bytes. Opting out exists and is loud: `verify_integrity=False`, at the call
258
307
  site, nowhere else.
259
308
 
260
- ## Not in 0.2.0
309
+ ## Not in 0.3.0
261
310
 
262
311
  Stated so nobody waits for them: no async API, no caching beyond ETag
263
312
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
264
313
  writes, and no DDS dependency of any kind. The wire is HTTP and a WebSocket.
265
314
 
315
+ `sd.asset` changed meaning in 0.3.0, which is why the version moved: it now
316
+ reads bytes and computes a hash, where in 0.2.0 it took a URL and a hash you
317
+ supplied. That second form is `sd.asset_ref`. A published release has to stay
318
+ reproducible from a public commit, so the API could not change under the same
319
+ number.
320
+
266
321
  On the registry, four things are deferred rather than missing, and the same
267
322
  four are on the 1.9 candidates list: **leases** (nothing expires; `ttl_sec` is
268
323
  advice to caches), **per-provider scopes** (one token, one operator),
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spatialdds"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "SpatialDDS over plain HTTPS and a WebSocket: a client for the web binding (Appendix N), and the reference registry behind one command."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -8,7 +8,8 @@ can act on.
8
8
  import spatialdds as sd
9
9
 
10
10
  ep = sd.connect("https://demo.spatialdds.org")
11
- svc = ep.search(geohash="9v6krc")[0]
11
+ svc = next(h for h in ep.search(geohash="9v6krc")
12
+ if h.serves("geo_anchor"))
12
13
  for a in svc.topic("geo_anchor").latched():
13
14
  print(a["anchor_id"], a["geopose"]["stamp"]["sec"]) # an int, not "17…"
14
15
 
@@ -30,12 +31,13 @@ __all__ = [
30
31
  # The record builders, re-exported so that `sd.circle(...)` reads the way
31
32
  # it does in the documentation. They live in `spatialdds.records`.
32
33
  "records", "circle", "bbox", "earth_fixed", "frame", "stamp", "geopose",
33
- "cov_pos3", "cov_none", "topic", "asset", "cells",
34
- "service_announce", "content_announce", "content_manifest", "geo_anchor",
35
- "service_uri", "content_uri",
34
+ "cov_pos3", "cov_none", "topic", "asset", "asset_ref", "cells",
35
+ "profile", "capabilities",
36
+ "service_announce", "service_manifest", "content_announce",
37
+ "content_manifest", "geo_anchor", "service_uri", "content_uri",
36
38
  ]
37
39
 
38
- __version__ = "0.2.0"
40
+ __version__ = "0.3.0"
39
41
  #: The SpatialDDS-spec commit whose Appendix N and generated artifacts this
40
42
  #: package implements. Package versions and specification versions move
41
43
  #: independently; this is the one that says what the wire looks like.
@@ -329,9 +329,10 @@ class Endpoint:
329
329
  def publish_content(self, content_id: str, *,
330
330
  position: Optional[Any] = None,
331
331
  coverage: Any = None, radius_m: float = 10.0,
332
- asset: Optional[str] = None,
332
+ asset: Any = None,
333
333
  media_type: str = "application/octet-stream",
334
- sha256: str = "", assets: Any = None,
334
+ sha256: str = "", asset_uri: str = "",
335
+ assets: Any = None,
335
336
  scene: Optional[str] = None,
336
337
  stream: str = "content",
337
338
  title: str = "", summary: str = "",
@@ -361,7 +362,8 @@ class Endpoint:
361
362
  "position=(lat, lon) or coverage=sd.circle(...).")
362
363
  items = list(assets) if assets else []
363
364
  if asset is not None:
364
- items.append(records.asset(asset, media_type, sha256=sha256))
365
+ items.append(self._asset_entry(asset, media_type, sha256,
366
+ asset_uri))
365
367
  man = records.content_manifest(
366
368
  content_id, authority=self.authority, scene=sc, assets=items,
367
369
  title=title, summary=summary, tags=tags, class_id=class_id,
@@ -377,6 +379,30 @@ class Endpoint:
377
379
  w.manifest = mw
378
380
  return w
379
381
 
382
+ def _asset_entry(self, asset: Any, media_type: str, sha256: str,
383
+ asset_uri: str) -> Dict[str, Any]:
384
+ """Either asset form, from whatever the caller actually had.
385
+
386
+ A dict is used as it stands -- `sd.asset()` and `sd.asset_ref()`
387
+ return those. A URL becomes a reference, with whatever hash was
388
+ supplied. Bytes, or a path to a file that exists, are hashed from the
389
+ data: `sd.asset()` does that, and the URI defaults to this endpoint's
390
+ own asset route under the authority it advertises, which is where the
391
+ bytes will be if you put them there. Pass `asset_uri=` for anywhere
392
+ else.
393
+ """
394
+ if isinstance(asset, dict):
395
+ return asset
396
+ if isinstance(asset, str) and asset.startswith(("http://", "https://")):
397
+ return records.asset_ref(asset, media_type=media_type,
398
+ hash=sha256)
399
+ name = ""
400
+ if not isinstance(asset, (bytes, bytearray)):
401
+ name = str(asset).rsplit("/", 1)[-1]
402
+ uri = asset_uri or (f"https://{self.authority}/assets/{name}"
403
+ if name else "")
404
+ return records.asset(asset, media_type=media_type, uri=uri)
405
+
380
406
  def publish_anchor(self, anchor_id: str, *, lat: float, lon: float,
381
407
  method: str, alt_m: float = 0.0,
382
408
  content: Optional[str] = None,
@@ -444,6 +470,18 @@ class Hit:
444
470
  f"{self.service_id or 'this service'} serves no {type_name!r} "
445
471
  f"topic; it offers: {have}")
446
472
 
473
+ def serves(self, type_name: str) -> bool:
474
+ """Does this service offer a topic of that type?
475
+
476
+ For picking a service out of a search result, which is what `search`
477
+ returns and what nobody should be indexing by position: the order is
478
+ the endpoint's, and it changes the moment somebody publishes another
479
+ service into the same cell. `search(...)[0]` on the front page read
480
+ the fountain's anchors until the page's own publish block ran, and
481
+ then read a VPS that serves none.
482
+ """
483
+ return any(t.get("type") == type_name for t in self.topics)
484
+
447
485
  def reaches(self, lat: float, lon: float, radius_m: float) -> bool:
448
486
  """Does the declared coverage come within `radius_m` of the point?"""
449
487
  return _coverage_reaches(self.coverage, lat, lon, radius_m)