spatialdds 0.4.0__tar.gz → 0.5.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 (148) hide show
  1. {spatialdds-0.4.0 → spatialdds-0.5.1}/PKG-INFO +46 -2
  2. {spatialdds-0.4.0 → spatialdds-0.5.1}/README.md +45 -1
  3. {spatialdds-0.4.0 → spatialdds-0.5.1}/pyproject.toml +1 -1
  4. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/__init__.py +1 -1
  5. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/commands.py +8 -2
  6. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/gw_http.py +85 -13
  7. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/publish.py +9 -1
  8. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds.egg-info/PKG-INFO +46 -2
  9. {spatialdds-0.4.0 → spatialdds-0.5.1}/setup.cfg +0 -0
  10. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/idl/core.idl +0 -0
  11. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/idl/discovery.idl +0 -0
  12. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/idl/types.idl +0 -0
  13. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
  14. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
  15. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
  16. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
  17. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
  18. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
  19. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
  20. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
  21. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
  22. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
  23. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
  24. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
  25. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
  26. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
  27. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
  28. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
  29. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
  30. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
  31. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
  32. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
  33. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
  34. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
  35. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
  36. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
  37. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
  38. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
  39. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
  40. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
  41. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
  42. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
  43. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
  44. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
  45. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
  46. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
  47. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
  48. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
  49. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
  50. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
  51. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
  52. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
  53. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
  54. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
  55. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
  56. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
  57. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
  58. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
  59. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
  60. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
  61. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
  62. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
  63. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
  64. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
  65. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
  66. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
  67. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
  68. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
  69. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
  70. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
  71. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
  72. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
  73. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
  74. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
  75. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
  76. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
  77. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
  78. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
  79. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
  80. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
  81. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
  82. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
  83. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
  84. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
  85. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
  86. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
  87. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
  88. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
  89. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
  90. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
  91. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
  92. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
  93. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
  94. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
  95. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
  96. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
  97. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
  98. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
  99. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
  100. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
  101. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
  102. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
  103. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
  104. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
  105. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
  106. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
  107. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
  108. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
  109. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
  110. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
  111. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
  112. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
  113. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
  114. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
  115. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
  116. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
  117. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
  118. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
  119. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
  120. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
  121. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
  122. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
  123. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
  124. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
  125. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/artifacts/serializer_tables.json +0 -0
  126. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/canonical.py +0 -0
  127. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/canonjson.py +0 -0
  128. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/client.py +0 -0
  129. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/demo/assets/duck.gltf +0 -0
  130. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/demo/assets/fountain-label.txt +0 -0
  131. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
  132. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/demo/fountain-anchors.mcap +0 -0
  133. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
  134. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
  135. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/errors.py +0 -0
  136. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/records.py +0 -0
  137. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/serve.py +0 -0
  138. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/__init__.py +0 -0
  139. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/gateway.py +0 -0
  140. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/idlmodel.py +0 -0
  141. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/pin.py +0 -0
  142. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/webjson.py +0 -0
  143. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds/server/xcdr2.py +0 -0
  144. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds.egg-info/SOURCES.txt +0 -0
  145. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds.egg-info/dependency_links.txt +0 -0
  146. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds.egg-info/entry_points.txt +0 -0
  147. {spatialdds-0.4.0 → spatialdds-0.5.1}/spatialdds.egg-info/requires.txt +0 -0
  148. {spatialdds-0.4.0 → spatialdds-0.5.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.4.0
3
+ Version: 0.5.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
@@ -379,13 +379,57 @@ operator token is the whole of it, and `docs/WIRE.md` documents the response
379
379
  shapes. Said plainly because "commands are supported" invites a reader to look
380
380
  for `hit.command(...)`, and there isn't one.
381
381
 
382
- ## Not in 0.4.0
382
+ ## New in 0.5.0: embedding the gateway against your own live source
383
+
384
+ 0.4.0 let a backend own the verbs. 0.5.0 lets it own the data, which took two
385
+ changes found by doing it:
386
+
387
+ | Call | What it does |
388
+ | --- | --- |
389
+ | `publish.apply_sample(st, topic, res, key, data)` | what a **new** subscriber replays, and what the collection GET returns |
390
+ | `gw_http.notify(app, topic, event)` | wakes the subscribers **already** connected |
391
+
392
+ They are halves of one contract. Until 0.5.0 only the first was public, so an
393
+ embedder could update the latched set and reach every client except the ones
394
+ currently watching — measured as three seconds of silence on an open socket,
395
+ with the same event arriving instantly once it was handed to the queue by
396
+ hand.
397
+
398
+ And `make_app` has a second switch, because it used to have one doing two
399
+ jobs:
400
+
401
+ | Argument | What it controls |
402
+ | --- | --- |
403
+ | `publish_enabled` | the PUT/DELETE write surface, and nothing else |
404
+ | `live_subscriptions` | whether an N.5 subscription stays open past the end of a recorded stream |
405
+
406
+ `live_subscriptions` defaults to following `publish_enabled`, so nothing that
407
+ already worked changed. An embedder with its own source passes
408
+ `publish_enabled=False, live_subscriptions=True` and gets the live lane with
409
+ no write surface at all — which was unreachable before, when keeping
410
+ subscribers connected meant exposing writes.
411
+
412
+ 0.5.1 fixes a defect in 0.4.0's seam that only its first real consumer could
413
+ find: `make_app(command_registry=...)` silently replaced a registry that was
414
+ empty at the time, because `Registry` was falsy when empty and the assignment
415
+ used `or`. Since an embedder must have the app before it can build the backend
416
+ that supplies the handlers, passing an empty registry and filling it a few
417
+ lines later is the normal shape — and every one of those registrations went to
418
+ an object the app had already discarded. If you called `register()` before
419
+ `make_app()`, 0.4.0 and 0.5.0 worked; if you called it after, the command lane
420
+ advertised nothing and refused every POST.
421
+
422
+ ## Not in 0.5.1
383
423
 
384
424
  Stated so nobody waits for them: no async API, no caching beyond ETag
385
425
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
386
426
  writes, no client-side command helper, and no DDS dependency of any kind. The
387
427
  wire is HTTP and a WebSocket.
388
428
 
429
+ 0.4.0 added the N.6 command surface described above; 0.5.0 made it usable
430
+ from an embedder with a live source, which is two releases for one capability
431
+ and the honest shape of finding the second half by using the first.
432
+
389
433
  0.3.1 was this page: two code blocks on it had no gate behind them and both
390
434
  were broken, and a long description is baked into the artifact, so the page on
391
435
  PyPI only heals with a release. Every block here is executed by a gate now,
@@ -355,13 +355,57 @@ operator token is the whole of it, and `docs/WIRE.md` documents the response
355
355
  shapes. Said plainly because "commands are supported" invites a reader to look
356
356
  for `hit.command(...)`, and there isn't one.
357
357
 
358
- ## Not in 0.4.0
358
+ ## New in 0.5.0: embedding the gateway against your own live source
359
+
360
+ 0.4.0 let a backend own the verbs. 0.5.0 lets it own the data, which took two
361
+ changes found by doing it:
362
+
363
+ | Call | What it does |
364
+ | --- | --- |
365
+ | `publish.apply_sample(st, topic, res, key, data)` | what a **new** subscriber replays, and what the collection GET returns |
366
+ | `gw_http.notify(app, topic, event)` | wakes the subscribers **already** connected |
367
+
368
+ They are halves of one contract. Until 0.5.0 only the first was public, so an
369
+ embedder could update the latched set and reach every client except the ones
370
+ currently watching — measured as three seconds of silence on an open socket,
371
+ with the same event arriving instantly once it was handed to the queue by
372
+ hand.
373
+
374
+ And `make_app` has a second switch, because it used to have one doing two
375
+ jobs:
376
+
377
+ | Argument | What it controls |
378
+ | --- | --- |
379
+ | `publish_enabled` | the PUT/DELETE write surface, and nothing else |
380
+ | `live_subscriptions` | whether an N.5 subscription stays open past the end of a recorded stream |
381
+
382
+ `live_subscriptions` defaults to following `publish_enabled`, so nothing that
383
+ already worked changed. An embedder with its own source passes
384
+ `publish_enabled=False, live_subscriptions=True` and gets the live lane with
385
+ no write surface at all — which was unreachable before, when keeping
386
+ subscribers connected meant exposing writes.
387
+
388
+ 0.5.1 fixes a defect in 0.4.0's seam that only its first real consumer could
389
+ find: `make_app(command_registry=...)` silently replaced a registry that was
390
+ empty at the time, because `Registry` was falsy when empty and the assignment
391
+ used `or`. Since an embedder must have the app before it can build the backend
392
+ that supplies the handlers, passing an empty registry and filling it a few
393
+ lines later is the normal shape — and every one of those registrations went to
394
+ an object the app had already discarded. If you called `register()` before
395
+ `make_app()`, 0.4.0 and 0.5.0 worked; if you called it after, the command lane
396
+ advertised nothing and refused every POST.
397
+
398
+ ## Not in 0.5.1
359
399
 
360
400
  Stated so nobody waits for them: no async API, no caching beyond ETag
361
401
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
362
402
  writes, no client-side command helper, and no DDS dependency of any kind. The
363
403
  wire is HTTP and a WebSocket.
364
404
 
405
+ 0.4.0 added the N.6 command surface described above; 0.5.0 made it usable
406
+ from an embedder with a live source, which is two releases for one capability
407
+ and the honest shape of finding the second half by using the first.
408
+
365
409
  0.3.1 was this page: two code blocks on it had no gate behind them and both
366
410
  were broken, and a long description is baked into the artifact, so the page on
367
411
  PyPI only heals with a release. Every block here is executed by a gate now,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spatialdds"
7
- version = "0.4.0"
7
+ version = "0.5.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"
@@ -37,7 +37,7 @@ __all__ = [
37
37
  "content_manifest", "geo_anchor", "service_uri", "content_uri",
38
38
  ]
39
39
 
40
- __version__ = "0.4.0"
40
+ __version__ = "0.5.1"
41
41
  #: The SpatialDDS-spec commit whose Appendix N and generated artifacts this
42
42
  #: package implements. Package versions and specification versions move
43
43
  #: independently; this is the one that says what the wire looks like.
@@ -127,5 +127,11 @@ class Registry:
127
127
  def types(self) -> list[str]:
128
128
  return sorted(self._handlers)
129
129
 
130
- def __bool__(self) -> bool:
131
- return bool(self._handlers)
130
+ # No `__bool__`. It returned `bool(self._handlers)`, which reads as a
131
+ # convenience and was a trap: `make_app` had `command_registry or
132
+ # Registry()`, so an empty registry -- the normal thing to pass, since an
133
+ # embedder needs the app before it can build the handlers -- was silently
134
+ # replaced by a fresh one, and every later `register()` was lost. Nothing
135
+ # else ever used the truthiness. A class whose emptiness is a legitimate
136
+ # state should not make `or` mean something surprising, so the method is
137
+ # gone rather than documented around.
@@ -476,12 +476,49 @@ def _written(request: web.Request, doc: Any, created: bool,
476
476
  **_cache_headers(gateway.OPERATOR), **extra})
477
477
 
478
478
 
479
- async def _broadcast(request: web.Request, topic: str, event: dict) -> None:
480
- """Hand an event to every subscriber currently on that topic."""
481
- for q in list(request.app["subs"].get(topic, ())):
479
+ def notify(app: web.Application, topic: str, event: dict) -> None:
480
+ """Hand an event to every subscriber currently on that topic.
481
+
482
+ **The second half of `publish.apply_sample`, and the reason this is
483
+ public.** `apply_sample` says what a *new* subscriber will replay;
484
+ this wakes the ones already connected. Until 0.5.0 only the first half
485
+ was public, so an embedder with a live backend could update the latched
486
+ set --- new subscribers and the collection GET saw it --- and had no way
487
+ to reach an open socket except to reimplement the two lines below against
488
+ `app["subs"]`, which is the one `make_app` key that is not a parameter.
489
+
490
+ Measured on 2026-10-17 while embedding this gateway against a live DDS
491
+ bus: `apply_sample` alone, with a subscriber connected and drained quiet,
492
+ delivered nothing for three seconds; the same event written into
493
+ `app["subs"]` by hand arrived immediately. One missing function, and the
494
+ workaround was vendoring a private one.
495
+
496
+ So the pair is the contract for a live backend:
497
+
498
+ publish.apply_sample(st, topic, res, key, data) # what replays
499
+ gw_http.notify(app, topic, {"event": "sample", # who hears it now
500
+ "topic": topic,
501
+ "key": key, "data": data})
502
+
503
+ Synchronous on purpose: it only puts onto unbounded queues, so there is
504
+ nothing to await, and a backend reading a DDS bus from a thread can call
505
+ it without arranging a loop.
506
+
507
+ Subscriptions only stay open to be woken when `live_subscriptions` is on
508
+ --- see `make_app`. Calling this against an endpoint serving a finished
509
+ recording is harmless and reaches nobody.
510
+ """
511
+ for q in list(app["subs"].get(topic, ())):
482
512
  q.put_nowait(event)
483
513
 
484
514
 
515
+ async def _broadcast(request: web.Request, topic: str, event: dict) -> None:
516
+ """`notify` for a request handler. Kept so its two call sites read as
517
+ they did; the seam is `notify`, which takes the app rather than a
518
+ request because a live backend has one and not the other."""
519
+ notify(request.app, topic, event)
520
+
521
+
485
522
  async def post_command(request: web.Request) -> web.Response:
486
523
  """N.6: a command is a POST to the resource it acts on.
487
524
 
@@ -733,13 +770,17 @@ async def subscribe(request: web.Request) -> web.StreamResponse:
733
770
  break
734
771
  await ws.send_str(json.dumps(ev, sort_keys=True))
735
772
 
736
- # With the publish surface on, the recording is one producer among
737
- # others and the stream is not finished: the socket stays open and
738
- # carries published samples and disposes as they are accepted. This is
739
- # the only read-side behaviour the flag changes, and it changes it in the
740
- # one direction a subscriber can already handle -- more events, same
741
- # envelope. With the flag off, everything below runs exactly as before.
742
- if request.app["publish"] and not ws.closed:
773
+ # The stream is not finished: the socket stays open and carries whatever
774
+ # arrives next -- samples and disposes a publisher sends, or anything an
775
+ # embedded live backend hands to `notify`. It changes the lane in the one
776
+ # direction a subscriber can already handle: more events, same envelope.
777
+ # With it off, everything below runs exactly as before.
778
+ #
779
+ # This was `app["publish"]` until 0.5.0, which meant a live backend had
780
+ # to expose a write surface to keep its subscribers connected. The two
781
+ # switches are separate now and `publish_enabled` still implies this one,
782
+ # so a caller that only ever passed the old flag is unaffected.
783
+ if request.app["live"] and not ws.closed:
743
784
  q: asyncio.Queue = asyncio.Queue()
744
785
  request.app["subs"].setdefault(topic, set()).add(q)
745
786
  try:
@@ -799,8 +840,28 @@ def make_app(state: gateway.State, base: str, token: str | None,
799
840
  publish_enabled: bool = False,
800
841
  journal: "publish.Journal | None" = None,
801
842
  assets: Path | None = None,
802
- command_registry: "commands.Registry | None" = None
803
- ) -> web.Application:
843
+ command_registry: "commands.Registry | None" = None,
844
+ live_subscriptions: bool | None = None) -> web.Application:
845
+ """The app, with two switches that used to be one.
846
+
847
+ `publish_enabled` means what its name says and nothing else: the
848
+ PUT/DELETE write surface. `live_subscriptions` controls socket lifetime
849
+ --- whether an N.5 subscription stays open after the recorded stream ends,
850
+ waiting for `notify`, instead of sending liveliness-false per key and
851
+ closing.
852
+
853
+ They were one flag until 0.5.0, which was a defect found by embedding:
854
+ a backend with its own live source had to switch publishing *on* to keep
855
+ its subscribers connected, which exposed a write surface it did not want.
856
+ Two behaviours under one flag named for the second.
857
+
858
+ `live_subscriptions` defaults to `None`, meaning "follow
859
+ `publish_enabled`", so every existing caller behaves exactly as it did ---
860
+ with publishing on, the recording is one producer among others and the
861
+ socket stays open, which is the sentence that was already true. An
862
+ embedder passes `publish_enabled=False, live_subscriptions=True` and gets
863
+ the live lane with no write surface at all.
864
+ """
804
865
  app = web.Application()
805
866
  app["state"] = state
806
867
  app["base"] = base.rstrip("/")
@@ -810,10 +871,21 @@ def make_app(state: gateway.State, base: str, token: str | None,
810
871
  # default page, and so that the surface's absence is something the
811
872
  # endpoint states rather than something a caller infers from a 405.
812
873
  app["publish"] = publish_enabled
874
+ app["live"] = publish_enabled if live_subscriptions is None \
875
+ else live_subscriptions
813
876
  # The N.6 seam. Empty is the normal state: a gateway serving a recording
814
877
  # has no verbs, and N.6 says a suite MUST NOT require a command surface
815
878
  # to exist. An embedder registers handlers on this.
816
- app["commands"] = command_registry or commands.Registry()
879
+ # `is not None`, not `or`. An empty registry is the normal thing to pass:
880
+ # an embedder needs the app before it can build the backend that supplies
881
+ # the handlers, so it passes a registry and populates it a few lines
882
+ # later. With `or` that registry was silently replaced -- `Registry` used
883
+ # to be falsy when empty -- and every later `register()` went to an object
884
+ # the app had already thrown away. Found by the web bridge, whose command
885
+ # lane advertised nothing and refused every POST while its own registry
886
+ # held the handler.
887
+ app["commands"] = (command_registry if command_registry is not None
888
+ else commands.Registry())
817
889
  app["journal"] = journal
818
890
  app["subs"] = {}
819
891
  app["assets"] = assets
@@ -207,7 +207,15 @@ def resource_for_put(st: gateway.State, scene: str, stream: str,
207
207
 
208
208
  def apply_sample(st: gateway.State, topic: str, res: gateway.Resource,
209
209
  key: str, data: dict) -> bool:
210
- """Put an instance into the latched set. True if it is new."""
210
+ """Put an instance into the latched set. True if it is new.
211
+
212
+ This is half of what a live backend needs, and the half that is easy to
213
+ mistake for all of it: it decides what a *new* subscriber replays and what
214
+ the collection GET returns, and it reaches nobody already on a socket.
215
+ `gw_http.notify(app, topic, event)` is the other half. An embedder that
216
+ calls only this one has a registry that is correct for every client except
217
+ the ones currently watching.
218
+ """
211
219
  created = key not in st.latched.get(topic, {})
212
220
  st.latched.setdefault(topic, {})[key] = gateway.Instance(
213
221
  key, data, webjson.digest(data))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdds
3
- Version: 0.4.0
3
+ Version: 0.5.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
@@ -379,13 +379,57 @@ operator token is the whole of it, and `docs/WIRE.md` documents the response
379
379
  shapes. Said plainly because "commands are supported" invites a reader to look
380
380
  for `hit.command(...)`, and there isn't one.
381
381
 
382
- ## Not in 0.4.0
382
+ ## New in 0.5.0: embedding the gateway against your own live source
383
+
384
+ 0.4.0 let a backend own the verbs. 0.5.0 lets it own the data, which took two
385
+ changes found by doing it:
386
+
387
+ | Call | What it does |
388
+ | --- | --- |
389
+ | `publish.apply_sample(st, topic, res, key, data)` | what a **new** subscriber replays, and what the collection GET returns |
390
+ | `gw_http.notify(app, topic, event)` | wakes the subscribers **already** connected |
391
+
392
+ They are halves of one contract. Until 0.5.0 only the first was public, so an
393
+ embedder could update the latched set and reach every client except the ones
394
+ currently watching — measured as three seconds of silence on an open socket,
395
+ with the same event arriving instantly once it was handed to the queue by
396
+ hand.
397
+
398
+ And `make_app` has a second switch, because it used to have one doing two
399
+ jobs:
400
+
401
+ | Argument | What it controls |
402
+ | --- | --- |
403
+ | `publish_enabled` | the PUT/DELETE write surface, and nothing else |
404
+ | `live_subscriptions` | whether an N.5 subscription stays open past the end of a recorded stream |
405
+
406
+ `live_subscriptions` defaults to following `publish_enabled`, so nothing that
407
+ already worked changed. An embedder with its own source passes
408
+ `publish_enabled=False, live_subscriptions=True` and gets the live lane with
409
+ no write surface at all — which was unreachable before, when keeping
410
+ subscribers connected meant exposing writes.
411
+
412
+ 0.5.1 fixes a defect in 0.4.0's seam that only its first real consumer could
413
+ find: `make_app(command_registry=...)` silently replaced a registry that was
414
+ empty at the time, because `Registry` was falsy when empty and the assignment
415
+ used `or`. Since an embedder must have the app before it can build the backend
416
+ that supplies the handlers, passing an empty registry and filling it a few
417
+ lines later is the normal shape — and every one of those registrations went to
418
+ an object the app had already discarded. If you called `register()` before
419
+ `make_app()`, 0.4.0 and 0.5.0 worked; if you called it after, the command lane
420
+ advertised nothing and refused every POST.
421
+
422
+ ## Not in 0.5.1
383
423
 
384
424
  Stated so nobody waits for them: no async API, no caching beyond ETag
385
425
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
386
426
  writes, no client-side command helper, and no DDS dependency of any kind. The
387
427
  wire is HTTP and a WebSocket.
388
428
 
429
+ 0.4.0 added the N.6 command surface described above; 0.5.0 made it usable
430
+ from an embedder with a live source, which is two releases for one capability
431
+ and the honest shape of finding the second half by using the first.
432
+
389
433
  0.3.1 was this page: two code blocks on it had no gate behind them and both
390
434
  were broken, and a long description is baked into the artifact, so the page on
391
435
  PyPI only heals with a release. Every block here is executed by a gate now,
File without changes