spatialdds 0.5.0__tar.gz → 0.5.2__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.5.0 → spatialdds-0.5.2}/PKG-INFO +21 -2
  2. {spatialdds-0.5.0 → spatialdds-0.5.2}/README.md +20 -1
  3. {spatialdds-0.5.0 → spatialdds-0.5.2}/pyproject.toml +1 -1
  4. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/__init__.py +1 -1
  5. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/commands.py +8 -2
  6. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/gw_http.py +59 -5
  7. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds.egg-info/PKG-INFO +21 -2
  8. {spatialdds-0.5.0 → spatialdds-0.5.2}/setup.cfg +0 -0
  9. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/idl/core.idl +0 -0
  10. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/idl/discovery.idl +0 -0
  11. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/idl/types.idl +0 -0
  12. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
  13. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
  14. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
  15. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
  16. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
  17. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
  18. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
  19. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
  20. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
  21. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
  22. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
  23. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
  24. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
  25. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
  26. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
  27. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
  28. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
  29. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
  30. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
  31. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
  32. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
  33. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
  34. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
  35. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
  36. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
  37. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
  38. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
  39. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
  40. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
  41. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
  42. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
  43. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
  44. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
  45. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
  46. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
  47. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
  48. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
  49. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
  50. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
  51. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
  52. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
  53. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
  54. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
  55. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
  56. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
  57. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
  58. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
  59. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
  60. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
  61. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
  62. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
  63. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
  64. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
  65. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
  66. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
  67. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
  68. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
  69. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
  70. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
  71. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
  72. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
  73. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
  74. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
  75. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
  76. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
  77. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
  78. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
  79. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
  80. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
  81. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
  82. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
  83. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
  84. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
  85. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
  86. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
  87. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
  88. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
  89. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
  90. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
  91. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
  92. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
  93. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
  94. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
  95. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
  96. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
  97. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
  98. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
  99. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
  100. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
  101. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
  102. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
  103. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
  104. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
  105. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
  106. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
  107. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
  108. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
  109. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
  110. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
  111. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
  112. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
  113. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
  114. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
  115. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
  116. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
  117. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
  118. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
  119. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
  120. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
  121. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
  122. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
  123. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
  124. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/artifacts/serializer_tables.json +0 -0
  125. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/canonical.py +0 -0
  126. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/canonjson.py +0 -0
  127. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/client.py +0 -0
  128. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/demo/assets/duck.gltf +0 -0
  129. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/demo/assets/fountain-label.txt +0 -0
  130. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
  131. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/demo/fountain-anchors.mcap +0 -0
  132. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
  133. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
  134. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/errors.py +0 -0
  135. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/records.py +0 -0
  136. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/serve.py +0 -0
  137. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/__init__.py +0 -0
  138. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/gateway.py +0 -0
  139. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/idlmodel.py +0 -0
  140. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/pin.py +0 -0
  141. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/publish.py +0 -0
  142. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/webjson.py +0 -0
  143. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds/server/xcdr2.py +0 -0
  144. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds.egg-info/SOURCES.txt +0 -0
  145. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds.egg-info/dependency_links.txt +0 -0
  146. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds.egg-info/entry_points.txt +0 -0
  147. {spatialdds-0.5.0 → spatialdds-0.5.2}/spatialdds.egg-info/requires.txt +0 -0
  148. {spatialdds-0.5.0 → spatialdds-0.5.2}/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.5.0
3
+ Version: 0.5.2
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
@@ -409,7 +409,26 @@ already worked changed. An embedder with its own source passes
409
409
  no write surface at all — which was unreachable before, when keeping
410
410
  subscribers connected meant exposing writes.
411
411
 
412
- ## Not in 0.5.0
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
+ 0.5.2 makes `notify` safe from the thread its own docstring invited. It put
423
+ onto the subscriber queue directly, and `asyncio.Queue.put_nowait` from a
424
+ foreign thread enqueues without waking the loop — so a backend reading a bus
425
+ in a thread fed a parked subscriber nothing until the loop woke for some other
426
+ reason. Busy endpoint, invisible; idle endpoint, silent. Measured: a bridge
427
+ pumping 202 samples into two registered subscriber queues delivered 0 live
428
+ events. If you call `notify` only from the serving loop, 0.5.0 and 0.5.1 were
429
+ correct for you.
430
+
431
+ ## Not in 0.5.2
413
432
 
414
433
  Stated so nobody waits for them: no async API, no caching beyond ETag
415
434
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
@@ -385,7 +385,26 @@ already worked changed. An embedder with its own source passes
385
385
  no write surface at all — which was unreachable before, when keeping
386
386
  subscribers connected meant exposing writes.
387
387
 
388
- ## Not in 0.5.0
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
+ 0.5.2 makes `notify` safe from the thread its own docstring invited. It put
399
+ onto the subscriber queue directly, and `asyncio.Queue.put_nowait` from a
400
+ foreign thread enqueues without waking the loop — so a backend reading a bus
401
+ in a thread fed a parked subscriber nothing until the loop woke for some other
402
+ reason. Busy endpoint, invisible; idle endpoint, silent. Measured: a bridge
403
+ pumping 202 samples into two registered subscriber queues delivered 0 live
404
+ events. If you call `notify` only from the serving loop, 0.5.0 and 0.5.1 were
405
+ correct for you.
406
+
407
+ ## Not in 0.5.2
389
408
 
390
409
  Stated so nobody waits for them: no async API, no caching beyond ETag
391
410
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spatialdds"
7
- version = "0.5.0"
7
+ version = "0.5.2"
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.5.0"
40
+ __version__ = "0.5.2"
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,6 +476,24 @@ def _written(request: web.Request, doc: Any, created: bool,
476
476
  **_cache_headers(gateway.OPERATOR), **extra})
477
477
 
478
478
 
479
+ def _running_loop():
480
+ """The loop running in *this* thread, or None. Never raises."""
481
+ try:
482
+ return asyncio.get_running_loop()
483
+ except RuntimeError:
484
+ return None
485
+
486
+
487
+ async def _remember_loop(app: web.Application) -> None:
488
+ """Record the serving loop, so `notify` can reach it from a thread.
489
+
490
+ An `on_startup` signal rather than `make_app`, because `make_app` is
491
+ ordinarily called before there is a loop to record -- an embedder builds
492
+ the app, then hands it to `run_app`.
493
+ """
494
+ app["loop"] = asyncio.get_running_loop()
495
+
496
+
479
497
  def notify(app: web.Application, topic: str, event: dict) -> None:
480
498
  """Hand an event to every subscriber currently on that topic.
481
499
 
@@ -500,16 +518,38 @@ def notify(app: web.Application, topic: str, event: dict) -> None:
500
518
  "topic": topic,
501
519
  "key": key, "data": data})
502
520
 
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.
521
+ Synchronous on purpose: there is nothing to await, so a backend reading a
522
+ DDS bus from a thread can call it without arranging a loop. **That use is
523
+ now actually safe**, which it was not in 0.5.0 and 0.5.1: this put onto
524
+ the queue directly, and `asyncio.Queue.put_nowait` from a foreign thread
525
+ enqueues without waking the selector, so a parked subscriber resumed only
526
+ when the loop happened to wake for some other reason. On a busy endpoint
527
+ that is immediate and the bug invisible; on an idle one the events sat
528
+ unseen. The docstring promised the thread case before the code delivered
529
+ it, which is the worse half of the defect.
506
530
 
507
531
  Subscriptions only stay open to be woken when `live_subscriptions` is on
508
532
  --- see `make_app`. Calling this against an endpoint serving a finished
509
533
  recording is harmless and reaches nobody.
510
534
  """
535
+ loop = app.get("loop")
511
536
  for q in list(app["subs"].get(topic, ())):
512
- q.put_nowait(event)
537
+ if loop is None or loop is _running_loop():
538
+ q.put_nowait(event)
539
+ else:
540
+ # `asyncio.Queue.put_nowait` is not thread-safe, and the part
541
+ # that bites is not the enqueue -- it is the wakeup. It resolves
542
+ # the parked getter's future directly, and resolving a future
543
+ # from a foreign thread schedules the callback without waking
544
+ # the selector, so the subscriber's `await q.get()` resumes only
545
+ # when the loop next happens to wake for some other reason. On a
546
+ # busy endpoint that is immediate and the bug is invisible; on an
547
+ # idle one the events sit in the queue.
548
+ #
549
+ # Measured on 2026-10-17: a bridge pumping 202 samples into two
550
+ # registered subscriber queues delivered 0 live events, while the
551
+ # same call from the loop thread delivered every one.
552
+ loop.call_soon_threadsafe(q.put_nowait, event)
513
553
 
514
554
 
515
555
  async def _broadcast(request: web.Request, topic: str, event: dict) -> None:
@@ -876,9 +916,23 @@ def make_app(state: gateway.State, base: str, token: str | None,
876
916
  # The N.6 seam. Empty is the normal state: a gateway serving a recording
877
917
  # has no verbs, and N.6 says a suite MUST NOT require a command surface
878
918
  # to exist. An embedder registers handlers on this.
879
- app["commands"] = command_registry or commands.Registry()
919
+ # `is not None`, not `or`. An empty registry is the normal thing to pass:
920
+ # an embedder needs the app before it can build the backend that supplies
921
+ # the handlers, so it passes a registry and populates it a few lines
922
+ # later. With `or` that registry was silently replaced -- `Registry` used
923
+ # to be falsy when empty -- and every later `register()` went to an object
924
+ # the app had already thrown away. Found by the web bridge, whose command
925
+ # lane advertised nothing and refused every POST while its own registry
926
+ # held the handler.
927
+ app["commands"] = (command_registry if command_registry is not None
928
+ else commands.Registry())
880
929
  app["journal"] = journal
881
930
  app["subs"] = {}
931
+ #: The serving loop, filled in at startup. `notify` needs it to hand an
932
+ #: event across a thread boundary safely; None until then, which is
933
+ #: correct -- nothing can be subscribed before the app is running.
934
+ app["loop"] = None
935
+ app.on_startup.append(_remember_loop)
882
936
  app["assets"] = assets
883
937
  app.add_routes([
884
938
  web.get("/", root),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdds
3
- Version: 0.5.0
3
+ Version: 0.5.2
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
@@ -409,7 +409,26 @@ already worked changed. An embedder with its own source passes
409
409
  no write surface at all — which was unreachable before, when keeping
410
410
  subscribers connected meant exposing writes.
411
411
 
412
- ## Not in 0.5.0
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
+ 0.5.2 makes `notify` safe from the thread its own docstring invited. It put
423
+ onto the subscriber queue directly, and `asyncio.Queue.put_nowait` from a
424
+ foreign thread enqueues without waking the loop — so a backend reading a bus
425
+ in a thread fed a parked subscriber nothing until the loop woke for some other
426
+ reason. Busy endpoint, invisible; idle endpoint, silent. Measured: a bridge
427
+ pumping 202 samples into two registered subscriber queues delivered 0 live
428
+ events. If you call `notify` only from the serving loop, 0.5.0 and 0.5.1 were
429
+ correct for you.
430
+
431
+ ## Not in 0.5.2
413
432
 
414
433
  Stated so nobody waits for them: no async API, no caching beyond ETag
415
434
  revalidation, no retry policy, no MCAP tooling beyond the journal the server
File without changes