spatialdds 0.2.0__tar.gz → 0.3.1__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.1}/PKG-INFO +104 -17
  2. {spatialdds-0.2.0 → spatialdds-0.3.1}/README.md +103 -16
  3. {spatialdds-0.2.0 → spatialdds-0.3.1}/pyproject.toml +1 -1
  4. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/__init__.py +7 -5
  5. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/client.py +41 -3
  6. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/records.py +228 -35
  7. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds.egg-info/PKG-INFO +104 -17
  8. {spatialdds-0.2.0 → spatialdds-0.3.1}/setup.cfg +0 -0
  9. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/idl/core.idl +0 -0
  10. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/idl/discovery.idl +0 -0
  11. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/idl/types.idl +0 -0
  12. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
  13. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
  14. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
  15. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
  16. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
  17. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
  18. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
  19. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
  20. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
  21. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
  22. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
  23. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
  24. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
  25. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
  26. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
  27. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
  28. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
  29. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
  30. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
  31. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
  32. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
  33. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
  34. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
  35. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
  36. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
  37. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
  38. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
  39. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
  40. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
  41. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
  42. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
  43. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
  44. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
  45. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
  46. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
  47. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
  48. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
  49. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
  50. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
  51. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
  52. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
  53. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
  54. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
  55. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
  56. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
  57. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
  58. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
  59. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
  60. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
  61. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
  62. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
  63. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
  64. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
  65. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
  66. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
  67. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
  68. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
  69. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
  70. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
  71. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
  72. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
  73. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
  74. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
  75. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
  76. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
  77. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
  78. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
  79. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
  80. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
  81. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
  82. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
  83. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
  84. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
  85. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
  86. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
  87. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
  88. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
  89. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
  90. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
  91. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
  92. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
  93. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
  94. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
  95. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
  96. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
  97. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
  98. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
  99. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
  100. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
  101. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
  102. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
  103. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
  104. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
  105. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
  106. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
  107. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
  108. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
  109. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
  110. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
  111. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
  112. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
  113. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
  114. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
  115. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
  116. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
  117. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
  118. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
  119. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
  120. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
  121. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
  122. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
  123. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
  124. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/artifacts/serializer_tables.json +0 -0
  125. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/canonical.py +0 -0
  126. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/canonjson.py +0 -0
  127. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/demo/assets/duck.gltf +0 -0
  128. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/demo/assets/fountain-label.txt +0 -0
  129. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
  130. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/demo/fountain-anchors.mcap +0 -0
  131. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
  132. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
  133. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/errors.py +0 -0
  134. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/serve.py +0 -0
  135. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/__init__.py +0 -0
  136. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/gateway.py +0 -0
  137. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/gw_http.py +0 -0
  138. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/idlmodel.py +0 -0
  139. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/pin.py +0 -0
  140. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/publish.py +0 -0
  141. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/webjson.py +0 -0
  142. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds/server/xcdr2.py +0 -0
  143. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds.egg-info/SOURCES.txt +0 -0
  144. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds.egg-info/dependency_links.txt +0 -0
  145. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds.egg-info/entry_points.txt +0 -0
  146. {spatialdds-0.2.0 → spatialdds-0.3.1}/spatialdds.egg-info/requires.txt +0 -0
  147. {spatialdds-0.2.0 → spatialdds-0.3.1}/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.1
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
@@ -43,6 +43,10 @@ not have to assemble member by member.
43
43
  specification versions move independently; `spatialdds.SPEC_COMMIT` is the one
44
44
  that says what the wire looks like.
45
45
 
46
+ The source, the reference gateway, the recordings and every gate behind the
47
+ claims on this page are at [https://github.com/OpenArCloud/spatialdds-web](https://github.com/OpenArCloud/spatialdds-web). Every code block below is executed
48
+ by one of those gates on every run, against an endpoint started for it.
49
+
46
50
  ## Your own registry, in one command
47
51
 
48
52
  ```sh
@@ -72,34 +76,101 @@ Appendix N's schemas make every member of a record required, so an `Announce`
72
76
  is eighteen members, a `ContentAnnounce` sixteen, a `GeoAnchor` nine. The
73
77
  builders take the arguments you actually have:
74
78
 
79
+ <!-- bar: verbs -->
75
80
  ```python
76
81
  import spatialdds as sd
82
+ ep = sd.connect("http://127.0.0.1:8787", token="not-a-secret", scene="mytown")
83
+ a = ep.publish_service(service_id="mytown-vps", kind="VPS",
84
+ coverage=sd.circle(30.2849, -97.7393, 500))
85
+ b = ep.publish_content(
86
+ content_id="duck", title="A duck", position=(30.2849, -97.7393),
87
+ asset=sd.asset_ref("https://cdn.example/duck.gltf",
88
+ media_type="model/gltf+json"))
89
+ c = ep.publish_anchor("anchor/duck-perch", lat=30.2849, lon=-97.7393,
90
+ method="VisualFix", sigma_enu_m=(0.25, 0.25, 0.5),
91
+ content="duck")
92
+ print("service", a.created, "| content", b.created, "| anchor", c.created)
93
+ print("manifest", b.manifest.doc["id"])
94
+ ```
77
95
 
78
- ep = sd.connect("http://127.0.0.1:8787", token="tok", scene="mytown")
79
- ep.publish_service(service_id="mytown-vps", kind="VPS",
80
- coverage=sd.circle(30.2849, -97.7393, 500))
81
- ep.publish_content(content_id="duck", title="Duck", position=(30.2849, -97.7393),
82
- asset="https://cdn.example/duck.gltf", sha256=digest)
83
- ep.publish_anchor("anchor/duck-perch", lat=30.2849, lon=-97.7393,
84
- method="VisualFix", sigma_enu_m=(0.25, 0.25, 0.5),
85
- content="duck")
96
+ ```console
97
+ service True | content True | anchor True
98
+ manifest spatialdds://demo.spatialdds.org/mytown/content/duck
86
99
  ```
87
100
 
88
101
  `publish_service` and `publish_content` are two PUTs each -- the manifest
89
102
  first, then the record that points at it -- and return what `publish` returns,
90
103
  with the manifest's own result on `.manifest`.
91
104
 
105
+ A full service contract is the same one call, with the claims stated:
106
+
107
+ <!-- bar: contract -->
108
+ ```python
109
+ import spatialdds as sd
110
+ ep = sd.connect("http://127.0.0.1:8787", token="not-a-secret", scene="mytown")
111
+ area = sd.circle(30.2849, -97.7393, 500)
112
+ w = ep.publish_service(
113
+ service_id="mytown-vps", kind="VPS", coverage=area,
114
+ caps=sd.capabilities(
115
+ supported=[sd.profile("spatial.core", minor=8)],
116
+ features=["web-binding/json"]),
117
+ manifest=sd.service_manifest(
118
+ "mytown-vps", "VPS", authority=ep.authority, scene="mytown",
119
+ profiles=["Core", "AR+Geo"],
120
+ request={"features_topic": "feat.keyframe"},
121
+ response={"minimal": "geo.fix"},
122
+ limits={"max_fps": 10},
123
+ auth={"scheme": "bearer",
124
+ "issuer": "https://registry.mytown.example"},
125
+ coverage=area, cells=sd.cells(area)))
126
+ print("profiles", w.manifest.doc["profiles"])
127
+ print("limits ", w.manifest.doc["limits"])
128
+ ```
129
+
130
+ ```console
131
+ profiles ['Core', 'AR+Geo']
132
+ limits {'max_fps': 10}
133
+ ```
134
+
135
+ Every one of those is something a service operator knows and a library cannot
136
+ guess. [The mytown walkthrough](https://github.com/OpenArCloud/spatialdds-web/blob/main/docs/MYTOWN.md) publishes that
137
+ and prints the whole served document;
138
+ [`gates/builders.py`](https://github.com/OpenArCloud/spatialdds-web/blob/main/gates/builders.py) rebuilds that
139
+ repository's hand-authored `austin-vps` and duck records through these calls
140
+ and compares them member by member, which is what makes "every member is
141
+ reachable from an argument" a check rather than a claim.
142
+
143
+ **Two forms for an asset, because there are two situations.**
144
+ `sd.asset(path_or_bytes, media_type=...)` reads the data and computes the hash
145
+ and the byte count itself: a hash nobody typed is a hash a reader can trust.
146
+ `sd.asset_ref(url, hash=..., media_type=...)` points at something somebody
147
+ else hosts, and the hash comes from whoever has the bytes. Pass no hash and
148
+ the reference carries no integrity claim, which costs a reader one thing:
149
+ `fetch_asset()` raises rather than reporting a check it could not perform
150
+ ([finding 31](https://github.com/OpenArCloud/spatialdds-web/blob/main/WEB-BINDING-FINDINGS.md)).
151
+ `publish_content` takes either, or a path, or bytes, or a URL.
152
+
92
153
  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`,
154
+ `sd.service_manifest`, `sd.content_announce`, `sd.content_manifest`,
155
+ `sd.geo_anchor`, with `sd.circle`, `sd.bbox`, `sd.geopose`, `sd.cov_pos3`,
156
+ `sd.cov_none`, `sd.profile`, `sd.capabilities`, `sd.asset_ref`,
95
157
  `sd.frame`,
96
158
  `sd.earth_fixed`, `sd.stamp`, `sd.topic`, `sd.asset`, `sd.cells`,
97
159
  `sd.service_uri` and `sd.content_uri`. Each returns a plain dict in canonical
98
160
  form, publishable as it stands and dumpable as the authoring JSON this
99
161
  repository's tools read. `spatialdds.records` is the module they live in.
100
162
 
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
163
+ **Conventions fill plumbing. They never fill claims.** A stamp of now, an
164
+ earth-fixed frame, a checksum computed from the record itself: plumbing, with
165
+ one sensible value each, and filling them saves you writing out what you had
166
+ no choice about. A profile list, a rate limit, a request topic, an auth
167
+ scheme, a title: claims, and no default for any of them is honest. State
168
+ nothing and you publish a minimal record, which is *correct*; state a
169
+ contract and you publish it in one call. There is nothing in between for a
170
+ library to invent.
171
+
172
+ **What they fill in, and what they refuse to.** The conventions above are
173
+ documented where they are applied: a stamp of now unless you pass `when=`, an
103
174
  earth-fixed frame unless you pass one, and URIs assembled under a stated
104
175
  authority -- never a guessed one. `ep.publish_*` gets the authority from the
105
176
  endpoint itself (`ep.authority`, which asks the resolver metadata, then the
@@ -160,7 +231,7 @@ the latched set is complete.
160
231
  ```python
161
232
  import spatialdds as sd
162
233
  ep = sd.connect("http://127.0.0.1:8787")
163
- svc = ep.search(geohash="9v6krc")[0]
234
+ svc = next(h for h in ep.search(geohash="9v6krc") if h.serves("geo_anchor"))
164
235
  for ev in svc.topic("geo_anchor").subscribe(timeout=10):
165
236
  if isinstance(ev, sd.EndOfReplay): print("latched set complete:", ev.replayed); break
166
237
  print(" ", ev.key, ev.data["method"], ev.data["geopose"]["stamp"]["sec"])
@@ -213,7 +284,7 @@ call goes away and the bar is one line.
213
284
 
214
285
  ## The surface
215
286
 
216
- | | |
287
+ | call | what it does |
217
288
  | --- | --- |
218
289
  | `sd.connect(base, token=None, verify_integrity=True, timeout=15.0, lifted=True, scene="default", authority=None)` | an `Endpoint`; nothing is fetched until you ask |
219
290
  | `ep.search(geohash=None, kinds=None)` | §3.3.0 search → a list of `Hit` |
@@ -227,6 +298,7 @@ call goes away and the bar is one line.
227
298
  | `ep.publish_anchor(anchor_id, lat=, lon=, method=, content=None, ...)` | build and PUT an anchor, optionally naming content |
228
299
  | `ep.withdraw(topic=, key=)` | DELETE: leaves the latched set and disposes |
229
300
  | `hit.service_id`, `.kind`, `.coverage`, `.topics` | the typed service manifest |
301
+ | `hit.serves(type_name)` | does this service offer a topic of that type? for picking one out of a search result |
230
302
  | `hit.topic(type_name)` | a `Topic`, URL taken from the manifest, never built |
231
303
  | `hit.reaches(lat, lon, radius_m)` | does the declared coverage reach a point |
232
304
  | `topic.type`, `.url`, `.name`, `.durability`, `.access`, `.fqn` | what the manifest said |
@@ -236,6 +308,9 @@ call goes away and the bar is one line.
236
308
  | `resolved.doc`, `.uri`, `.etag`, `.assets` | the resolved manifest |
237
309
  | `resolved.asset_url(i)`, `.fetch_asset(i)` | the URL it would fetch, and the verified bytes |
238
310
  | `written.doc`, `.etag`, `.created`, `.manifest` | what a publish returned |
311
+ | `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 |
312
+ | `sd.capabilities(supported=, preferred=, features=)`, `sd.profile(name, minor=)` | what a service supports, for `service_announce(caps=)` |
313
+ | `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
314
  | `sd.records` | the builders, listed above |
240
315
  | `sd.lift(fqn, doc)` | the lift on its own, for JSON you already have |
241
316
  | `sd.SPEC_COMMIT`, `sd.APPENDIX_N`, `sd.__version__` | what this speaks |
@@ -260,7 +335,7 @@ One family, built from the endpoint's own N.8 problem documents — `status`,
260
335
  `type`, `detail` carried, not re-invented. Everything below derives from
261
336
  `SpatialDDSError`, so one `except` catches the lot.
262
337
 
263
- | | |
338
+ | exception | raised when |
264
339
  | --- | --- |
265
340
  | `Unreachable` | the endpoint did not answer; names the URL and the serve line |
266
341
  | `Unauthorized` | 401; says to pass `token=` and where the token is printed |
@@ -281,12 +356,24 @@ Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
281
356
  the bytes. Opting out exists and is loud: `verify_integrity=False`, at the call
282
357
  site, nowhere else.
283
358
 
284
- ## Not in 0.2.0
359
+ ## Not in 0.3.1
285
360
 
286
361
  Stated so nobody waits for them: no async API, no caching beyond ETag
287
362
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
288
363
  writes, and no DDS dependency of any kind. The wire is HTTP and a WebSocket.
289
364
 
365
+ 0.3.1 is this page: two code blocks on it had no gate behind them and both
366
+ were broken, and a long description is baked into the artifact, so the page on
367
+ PyPI only heals with a release. Every block here is executed by a gate now,
368
+ and the gate also refuses a relative link, an empty table header and a serve
369
+ line naming a flag that does not exist.
370
+
371
+ `sd.asset` changed meaning in 0.3.0, which is why that version moved: it now
372
+ reads bytes and computes a hash, where in 0.2.0 it took a URL and a hash you
373
+ supplied. That second form is `sd.asset_ref`. A published release has to stay
374
+ reproducible from a public commit, so the API could not change under the same
375
+ number.
376
+
290
377
  On the registry, four things are deferred rather than missing, and the same
291
378
  four are on the 1.9 candidates list: **leases** (nothing expires; `ttl_sec` is
292
379
  advice to caches), **per-provider scopes** (one token, one operator),
@@ -19,6 +19,10 @@ not have to assemble member by member.
19
19
  specification versions move independently; `spatialdds.SPEC_COMMIT` is the one
20
20
  that says what the wire looks like.
21
21
 
22
+ The source, the reference gateway, the recordings and every gate behind the
23
+ claims on this page are at [https://github.com/OpenArCloud/spatialdds-web](https://github.com/OpenArCloud/spatialdds-web). Every code block below is executed
24
+ by one of those gates on every run, against an endpoint started for it.
25
+
22
26
  ## Your own registry, in one command
23
27
 
24
28
  ```sh
@@ -48,34 +52,101 @@ Appendix N's schemas make every member of a record required, so an `Announce`
48
52
  is eighteen members, a `ContentAnnounce` sixteen, a `GeoAnchor` nine. The
49
53
  builders take the arguments you actually have:
50
54
 
55
+ <!-- bar: verbs -->
51
56
  ```python
52
57
  import spatialdds as sd
58
+ ep = sd.connect("http://127.0.0.1:8787", token="not-a-secret", scene="mytown")
59
+ a = ep.publish_service(service_id="mytown-vps", kind="VPS",
60
+ coverage=sd.circle(30.2849, -97.7393, 500))
61
+ b = ep.publish_content(
62
+ content_id="duck", title="A duck", position=(30.2849, -97.7393),
63
+ asset=sd.asset_ref("https://cdn.example/duck.gltf",
64
+ media_type="model/gltf+json"))
65
+ c = ep.publish_anchor("anchor/duck-perch", lat=30.2849, lon=-97.7393,
66
+ method="VisualFix", sigma_enu_m=(0.25, 0.25, 0.5),
67
+ content="duck")
68
+ print("service", a.created, "| content", b.created, "| anchor", c.created)
69
+ print("manifest", b.manifest.doc["id"])
70
+ ```
53
71
 
54
- ep = sd.connect("http://127.0.0.1:8787", token="tok", scene="mytown")
55
- ep.publish_service(service_id="mytown-vps", kind="VPS",
56
- coverage=sd.circle(30.2849, -97.7393, 500))
57
- ep.publish_content(content_id="duck", title="Duck", position=(30.2849, -97.7393),
58
- asset="https://cdn.example/duck.gltf", sha256=digest)
59
- ep.publish_anchor("anchor/duck-perch", lat=30.2849, lon=-97.7393,
60
- method="VisualFix", sigma_enu_m=(0.25, 0.25, 0.5),
61
- content="duck")
72
+ ```console
73
+ service True | content True | anchor True
74
+ manifest spatialdds://demo.spatialdds.org/mytown/content/duck
62
75
  ```
63
76
 
64
77
  `publish_service` and `publish_content` are two PUTs each -- the manifest
65
78
  first, then the record that points at it -- and return what `publish` returns,
66
79
  with the manifest's own result on `.manifest`.
67
80
 
81
+ A full service contract is the same one call, with the claims stated:
82
+
83
+ <!-- bar: contract -->
84
+ ```python
85
+ import spatialdds as sd
86
+ ep = sd.connect("http://127.0.0.1:8787", token="not-a-secret", scene="mytown")
87
+ area = sd.circle(30.2849, -97.7393, 500)
88
+ w = ep.publish_service(
89
+ service_id="mytown-vps", kind="VPS", coverage=area,
90
+ caps=sd.capabilities(
91
+ supported=[sd.profile("spatial.core", minor=8)],
92
+ features=["web-binding/json"]),
93
+ manifest=sd.service_manifest(
94
+ "mytown-vps", "VPS", authority=ep.authority, scene="mytown",
95
+ profiles=["Core", "AR+Geo"],
96
+ request={"features_topic": "feat.keyframe"},
97
+ response={"minimal": "geo.fix"},
98
+ limits={"max_fps": 10},
99
+ auth={"scheme": "bearer",
100
+ "issuer": "https://registry.mytown.example"},
101
+ coverage=area, cells=sd.cells(area)))
102
+ print("profiles", w.manifest.doc["profiles"])
103
+ print("limits ", w.manifest.doc["limits"])
104
+ ```
105
+
106
+ ```console
107
+ profiles ['Core', 'AR+Geo']
108
+ limits {'max_fps': 10}
109
+ ```
110
+
111
+ Every one of those is something a service operator knows and a library cannot
112
+ guess. [The mytown walkthrough](https://github.com/OpenArCloud/spatialdds-web/blob/main/docs/MYTOWN.md) publishes that
113
+ and prints the whole served document;
114
+ [`gates/builders.py`](https://github.com/OpenArCloud/spatialdds-web/blob/main/gates/builders.py) rebuilds that
115
+ repository's hand-authored `austin-vps` and duck records through these calls
116
+ and compares them member by member, which is what makes "every member is
117
+ reachable from an argument" a check rather than a claim.
118
+
119
+ **Two forms for an asset, because there are two situations.**
120
+ `sd.asset(path_or_bytes, media_type=...)` reads the data and computes the hash
121
+ and the byte count itself: a hash nobody typed is a hash a reader can trust.
122
+ `sd.asset_ref(url, hash=..., media_type=...)` points at something somebody
123
+ else hosts, and the hash comes from whoever has the bytes. Pass no hash and
124
+ the reference carries no integrity claim, which costs a reader one thing:
125
+ `fetch_asset()` raises rather than reporting a check it could not perform
126
+ ([finding 31](https://github.com/OpenArCloud/spatialdds-web/blob/main/WEB-BINDING-FINDINGS.md)).
127
+ `publish_content` takes either, or a path, or bytes, or a URL.
128
+
68
129
  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`,
130
+ `sd.service_manifest`, `sd.content_announce`, `sd.content_manifest`,
131
+ `sd.geo_anchor`, with `sd.circle`, `sd.bbox`, `sd.geopose`, `sd.cov_pos3`,
132
+ `sd.cov_none`, `sd.profile`, `sd.capabilities`, `sd.asset_ref`,
71
133
  `sd.frame`,
72
134
  `sd.earth_fixed`, `sd.stamp`, `sd.topic`, `sd.asset`, `sd.cells`,
73
135
  `sd.service_uri` and `sd.content_uri`. Each returns a plain dict in canonical
74
136
  form, publishable as it stands and dumpable as the authoring JSON this
75
137
  repository's tools read. `spatialdds.records` is the module they live in.
76
138
 
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
139
+ **Conventions fill plumbing. They never fill claims.** A stamp of now, an
140
+ earth-fixed frame, a checksum computed from the record itself: plumbing, with
141
+ one sensible value each, and filling them saves you writing out what you had
142
+ no choice about. A profile list, a rate limit, a request topic, an auth
143
+ scheme, a title: claims, and no default for any of them is honest. State
144
+ nothing and you publish a minimal record, which is *correct*; state a
145
+ contract and you publish it in one call. There is nothing in between for a
146
+ library to invent.
147
+
148
+ **What they fill in, and what they refuse to.** The conventions above are
149
+ documented where they are applied: a stamp of now unless you pass `when=`, an
79
150
  earth-fixed frame unless you pass one, and URIs assembled under a stated
80
151
  authority -- never a guessed one. `ep.publish_*` gets the authority from the
81
152
  endpoint itself (`ep.authority`, which asks the resolver metadata, then the
@@ -136,7 +207,7 @@ the latched set is complete.
136
207
  ```python
137
208
  import spatialdds as sd
138
209
  ep = sd.connect("http://127.0.0.1:8787")
139
- svc = ep.search(geohash="9v6krc")[0]
210
+ svc = next(h for h in ep.search(geohash="9v6krc") if h.serves("geo_anchor"))
140
211
  for ev in svc.topic("geo_anchor").subscribe(timeout=10):
141
212
  if isinstance(ev, sd.EndOfReplay): print("latched set complete:", ev.replayed); break
142
213
  print(" ", ev.key, ev.data["method"], ev.data["geopose"]["stamp"]["sec"])
@@ -189,7 +260,7 @@ call goes away and the bar is one line.
189
260
 
190
261
  ## The surface
191
262
 
192
- | | |
263
+ | call | what it does |
193
264
  | --- | --- |
194
265
  | `sd.connect(base, token=None, verify_integrity=True, timeout=15.0, lifted=True, scene="default", authority=None)` | an `Endpoint`; nothing is fetched until you ask |
195
266
  | `ep.search(geohash=None, kinds=None)` | §3.3.0 search → a list of `Hit` |
@@ -203,6 +274,7 @@ call goes away and the bar is one line.
203
274
  | `ep.publish_anchor(anchor_id, lat=, lon=, method=, content=None, ...)` | build and PUT an anchor, optionally naming content |
204
275
  | `ep.withdraw(topic=, key=)` | DELETE: leaves the latched set and disposes |
205
276
  | `hit.service_id`, `.kind`, `.coverage`, `.topics` | the typed service manifest |
277
+ | `hit.serves(type_name)` | does this service offer a topic of that type? for picking one out of a search result |
206
278
  | `hit.topic(type_name)` | a `Topic`, URL taken from the manifest, never built |
207
279
  | `hit.reaches(lat, lon, radius_m)` | does the declared coverage reach a point |
208
280
  | `topic.type`, `.url`, `.name`, `.durability`, `.access`, `.fqn` | what the manifest said |
@@ -212,6 +284,9 @@ call goes away and the bar is one line.
212
284
  | `resolved.doc`, `.uri`, `.etag`, `.assets` | the resolved manifest |
213
285
  | `resolved.asset_url(i)`, `.fetch_asset(i)` | the URL it would fetch, and the verified bytes |
214
286
  | `written.doc`, `.etag`, `.created`, `.manifest` | what a publish returned |
287
+ | `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 |
288
+ | `sd.capabilities(supported=, preferred=, features=)`, `sd.profile(name, minor=)` | what a service supports, for `service_announce(caps=)` |
289
+ | `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
290
  | `sd.records` | the builders, listed above |
216
291
  | `sd.lift(fqn, doc)` | the lift on its own, for JSON you already have |
217
292
  | `sd.SPEC_COMMIT`, `sd.APPENDIX_N`, `sd.__version__` | what this speaks |
@@ -236,7 +311,7 @@ One family, built from the endpoint's own N.8 problem documents — `status`,
236
311
  `type`, `detail` carried, not re-invented. Everything below derives from
237
312
  `SpatialDDSError`, so one `except` catches the lot.
238
313
 
239
- | | |
314
+ | exception | raised when |
240
315
  | --- | --- |
241
316
  | `Unreachable` | the endpoint did not answer; names the URL and the serve line |
242
317
  | `Unauthorized` | 401; says to pass `token=` and where the token is printed |
@@ -257,12 +332,24 @@ Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
257
332
  the bytes. Opting out exists and is loud: `verify_integrity=False`, at the call
258
333
  site, nowhere else.
259
334
 
260
- ## Not in 0.2.0
335
+ ## Not in 0.3.1
261
336
 
262
337
  Stated so nobody waits for them: no async API, no caching beyond ETag
263
338
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
264
339
  writes, and no DDS dependency of any kind. The wire is HTTP and a WebSocket.
265
340
 
341
+ 0.3.1 is this page: two code blocks on it had no gate behind them and both
342
+ were broken, and a long description is baked into the artifact, so the page on
343
+ PyPI only heals with a release. Every block here is executed by a gate now,
344
+ and the gate also refuses a relative link, an empty table header and a serve
345
+ line naming a flag that does not exist.
346
+
347
+ `sd.asset` changed meaning in 0.3.0, which is why that version moved: it now
348
+ reads bytes and computes a hash, where in 0.2.0 it took a URL and a hash you
349
+ supplied. That second form is `sd.asset_ref`. A published release has to stay
350
+ reproducible from a public commit, so the API could not change under the same
351
+ number.
352
+
266
353
  On the registry, four things are deferred rather than missing, and the same
267
354
  four are on the 1.9 candidates list: **leases** (nothing expires; `ttl_sec` is
268
355
  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.1"
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.1"
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)