spatialdds 0.4.0__tar.gz → 0.5.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 (148) hide show
  1. {spatialdds-0.4.0 → spatialdds-0.5.0}/PKG-INFO +36 -2
  2. {spatialdds-0.4.0 → spatialdds-0.5.0}/README.md +35 -1
  3. {spatialdds-0.4.0 → spatialdds-0.5.0}/pyproject.toml +1 -1
  4. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/__init__.py +1 -1
  5. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/gw_http.py +75 -12
  6. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/publish.py +9 -1
  7. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds.egg-info/PKG-INFO +36 -2
  8. {spatialdds-0.4.0 → spatialdds-0.5.0}/setup.cfg +0 -0
  9. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/idl/core.idl +0 -0
  10. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/idl/discovery.idl +0 -0
  11. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/idl/types.idl +0 -0
  12. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
  13. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
  14. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
  15. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
  16. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
  17. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
  18. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
  19. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
  20. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
  21. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
  22. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
  23. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
  24. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
  25. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
  26. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
  27. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
  28. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
  29. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
  30. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
  31. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
  32. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
  33. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
  34. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
  35. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
  36. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
  37. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
  38. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
  39. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
  40. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
  41. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
  42. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
  43. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
  44. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
  45. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
  46. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
  47. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
  48. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
  49. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
  50. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
  51. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
  52. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
  53. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
  54. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
  55. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
  56. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
  57. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
  58. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
  59. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
  60. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
  61. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
  62. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
  63. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
  64. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
  65. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
  66. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
  67. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
  68. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
  69. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
  70. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
  71. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
  72. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
  73. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
  74. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
  75. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
  76. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
  77. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
  78. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
  79. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
  80. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
  81. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
  82. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
  83. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
  84. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
  85. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
  86. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
  87. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
  88. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
  89. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
  90. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
  91. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
  92. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
  93. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
  94. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
  95. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
  96. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
  97. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
  98. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
  99. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
  100. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
  101. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
  102. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
  103. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
  104. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
  105. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
  106. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
  107. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
  108. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
  109. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
  110. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
  111. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
  112. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
  113. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
  114. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
  115. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
  116. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
  117. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
  118. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
  119. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
  120. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
  121. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
  122. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
  123. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
  124. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/artifacts/serializer_tables.json +0 -0
  125. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/canonical.py +0 -0
  126. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/canonjson.py +0 -0
  127. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/client.py +0 -0
  128. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/demo/assets/duck.gltf +0 -0
  129. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/demo/assets/fountain-label.txt +0 -0
  130. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
  131. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/demo/fountain-anchors.mcap +0 -0
  132. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
  133. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
  134. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/errors.py +0 -0
  135. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/records.py +0 -0
  136. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/serve.py +0 -0
  137. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/__init__.py +0 -0
  138. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/commands.py +0 -0
  139. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/gateway.py +0 -0
  140. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/idlmodel.py +0 -0
  141. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/pin.py +0 -0
  142. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/webjson.py +0 -0
  143. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds/server/xcdr2.py +0 -0
  144. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds.egg-info/SOURCES.txt +0 -0
  145. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds.egg-info/dependency_links.txt +0 -0
  146. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds.egg-info/entry_points.txt +0 -0
  147. {spatialdds-0.4.0 → spatialdds-0.5.0}/spatialdds.egg-info/requires.txt +0 -0
  148. {spatialdds-0.4.0 → spatialdds-0.5.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.4.0
3
+ Version: 0.5.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
@@ -379,13 +379,47 @@ 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
+ ## Not in 0.5.0
383
413
 
384
414
  Stated so nobody waits for them: no async API, no caching beyond ETag
385
415
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
386
416
  writes, no client-side command helper, and no DDS dependency of any kind. The
387
417
  wire is HTTP and a WebSocket.
388
418
 
419
+ 0.4.0 added the N.6 command surface described above; 0.5.0 made it usable
420
+ from an embedder with a live source, which is two releases for one capability
421
+ and the honest shape of finding the second half by using the first.
422
+
389
423
  0.3.1 was this page: two code blocks on it had no gate behind them and both
390
424
  were broken, and a long description is baked into the artifact, so the page on
391
425
  PyPI only heals with a release. Every block here is executed by a gate now,
@@ -355,13 +355,47 @@ 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
+ ## Not in 0.5.0
359
389
 
360
390
  Stated so nobody waits for them: no async API, no caching beyond ETag
361
391
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
362
392
  writes, no client-side command helper, and no DDS dependency of any kind. The
363
393
  wire is HTTP and a WebSocket.
364
394
 
395
+ 0.4.0 added the N.6 command surface described above; 0.5.0 made it usable
396
+ from an embedder with a live source, which is two releases for one capability
397
+ and the honest shape of finding the second half by using the first.
398
+
365
399
  0.3.1 was this page: two code blocks on it had no gate behind them and both
366
400
  were broken, and a long description is baked into the artifact, so the page on
367
401
  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.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"
@@ -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.0"
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.
@@ -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,6 +871,8 @@ 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.
@@ -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.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
@@ -379,13 +379,47 @@ 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
+ ## Not in 0.5.0
383
413
 
384
414
  Stated so nobody waits for them: no async API, no caching beyond ETag
385
415
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
386
416
  writes, no client-side command helper, and no DDS dependency of any kind. The
387
417
  wire is HTTP and a WebSocket.
388
418
 
419
+ 0.4.0 added the N.6 command surface described above; 0.5.0 made it usable
420
+ from an embedder with a live source, which is two releases for one capability
421
+ and the honest shape of finding the second half by using the first.
422
+
389
423
  0.3.1 was this page: two code blocks on it had no gate behind them and both
390
424
  were broken, and a long description is baked into the artifact, so the page on
391
425
  PyPI only heals with a release. Every block here is executed by a gate now,
File without changes