spatialdds 0.3.1__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.
- {spatialdds-0.3.1 → spatialdds-0.5.0}/PKG-INFO +62 -4
- {spatialdds-0.3.1 → spatialdds-0.5.0}/README.md +61 -3
- {spatialdds-0.3.1 → spatialdds-0.5.0}/pyproject.toml +1 -1
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/__init__.py +1 -1
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/serve.py +45 -4
- spatialdds-0.5.0/spatialdds/server/commands.py +131 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/gateway.py +26 -6
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/gw_http.py +181 -13
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/publish.py +9 -1
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds.egg-info/PKG-INFO +62 -4
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds.egg-info/SOURCES.txt +1 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/setup.cfg +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/idl/core.idl +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/idl/discovery.idl +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/idl/types.idl +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/artifacts/serializer_tables.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/canonical.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/canonjson.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/client.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/demo/assets/duck.gltf +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/demo/assets/fountain-label.txt +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/demo/fountain-anchors.mcap +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/errors.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/records.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/__init__.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/idlmodel.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/pin.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/webjson.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds/server/xcdr2.py +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds.egg-info/dependency_links.txt +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds.egg-info/entry_points.txt +0 -0
- {spatialdds-0.3.1 → spatialdds-0.5.0}/spatialdds.egg-info/requires.txt +0 -0
- {spatialdds-0.3.1 → 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.
|
|
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
|
|
@@ -356,13 +356,71 @@ Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
|
|
|
356
356
|
the bytes. Opting out exists and is loud: `verify_integrity=False`, at the call
|
|
357
357
|
site, nowhere else.
|
|
358
358
|
|
|
359
|
-
##
|
|
359
|
+
## New in 0.4.0: a command surface on the server half
|
|
360
|
+
|
|
361
|
+
`spatialdds[server]` now implements Appendix N.6. A command is a POST to the
|
|
362
|
+
lane that accepts one, and an embedder registers what a verb means:
|
|
363
|
+
|
|
364
|
+
| Part | What it is |
|
|
365
|
+
| --- | --- |
|
|
366
|
+
| the seam | `spatialdds.server.commands.Registry().register(fqn, handler)` |
|
|
367
|
+
| a handler gets | the validated, lifted record — the same JSON a GET would serve |
|
|
368
|
+
| a handler returns | `Outcome.accept()` or `Outcome.decline("why")` |
|
|
369
|
+
| the wire | `202` accepted, `409` declined with the reason, `405` where no handler is registered |
|
|
370
|
+
|
|
371
|
+
`python -m spatialdds.serve --demo` registers one so the surface is reachable
|
|
372
|
+
without writing a backend. An endpoint with no handlers is northbound-only and
|
|
373
|
+
every command POST is a clean `405`, which is what N.6 requires and what every
|
|
374
|
+
earlier version did.
|
|
375
|
+
|
|
376
|
+
**The client half has no command method.** This release adds the surface, not
|
|
377
|
+
a helper for calling it — `requests.post(topic_url, json=record)` with the
|
|
378
|
+
operator token is the whole of it, and `docs/WIRE.md` documents the response
|
|
379
|
+
shapes. Said plainly because "commands are supported" invites a reader to look
|
|
380
|
+
for `hit.command(...)`, and there isn't one.
|
|
381
|
+
|
|
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
|
|
360
413
|
|
|
361
414
|
Stated so nobody waits for them: no async API, no caching beyond ETag
|
|
362
415
|
revalidation, no retry policy, no MCAP tooling beyond the journal the server
|
|
363
|
-
writes, and no DDS dependency of any kind. The
|
|
416
|
+
writes, no client-side command helper, and no DDS dependency of any kind. The
|
|
417
|
+
wire is HTTP and a WebSocket.
|
|
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.
|
|
364
422
|
|
|
365
|
-
0.3.1
|
|
423
|
+
0.3.1 was this page: two code blocks on it had no gate behind them and both
|
|
366
424
|
were broken, and a long description is baked into the artifact, so the page on
|
|
367
425
|
PyPI only heals with a release. Every block here is executed by a gate now,
|
|
368
426
|
and the gate also refuses a relative link, an empty table header and a serve
|
|
@@ -332,13 +332,71 @@ Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
|
|
|
332
332
|
the bytes. Opting out exists and is loud: `verify_integrity=False`, at the call
|
|
333
333
|
site, nowhere else.
|
|
334
334
|
|
|
335
|
-
##
|
|
335
|
+
## New in 0.4.0: a command surface on the server half
|
|
336
|
+
|
|
337
|
+
`spatialdds[server]` now implements Appendix N.6. A command is a POST to the
|
|
338
|
+
lane that accepts one, and an embedder registers what a verb means:
|
|
339
|
+
|
|
340
|
+
| Part | What it is |
|
|
341
|
+
| --- | --- |
|
|
342
|
+
| the seam | `spatialdds.server.commands.Registry().register(fqn, handler)` |
|
|
343
|
+
| a handler gets | the validated, lifted record — the same JSON a GET would serve |
|
|
344
|
+
| a handler returns | `Outcome.accept()` or `Outcome.decline("why")` |
|
|
345
|
+
| the wire | `202` accepted, `409` declined with the reason, `405` where no handler is registered |
|
|
346
|
+
|
|
347
|
+
`python -m spatialdds.serve --demo` registers one so the surface is reachable
|
|
348
|
+
without writing a backend. An endpoint with no handlers is northbound-only and
|
|
349
|
+
every command POST is a clean `405`, which is what N.6 requires and what every
|
|
350
|
+
earlier version did.
|
|
351
|
+
|
|
352
|
+
**The client half has no command method.** This release adds the surface, not
|
|
353
|
+
a helper for calling it — `requests.post(topic_url, json=record)` with the
|
|
354
|
+
operator token is the whole of it, and `docs/WIRE.md` documents the response
|
|
355
|
+
shapes. Said plainly because "commands are supported" invites a reader to look
|
|
356
|
+
for `hit.command(...)`, and there isn't one.
|
|
357
|
+
|
|
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
|
|
336
389
|
|
|
337
390
|
Stated so nobody waits for them: no async API, no caching beyond ETag
|
|
338
391
|
revalidation, no retry policy, no MCAP tooling beyond the journal the server
|
|
339
|
-
writes, and no DDS dependency of any kind. The
|
|
392
|
+
writes, no client-side command helper, and no DDS dependency of any kind. The
|
|
393
|
+
wire is HTTP and a WebSocket.
|
|
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.
|
|
340
398
|
|
|
341
|
-
0.3.1
|
|
399
|
+
0.3.1 was this page: two code blocks on it had no gate behind them and both
|
|
342
400
|
were broken, and a long description is baked into the artifact, so the page on
|
|
343
401
|
PyPI only heals with a release. Every block here is executed by a gate now,
|
|
344
402
|
and the gate also refuses a relative link, an empty table header and a serve
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spatialdds"
|
|
7
|
-
version = "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.
|
|
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.
|
|
@@ -42,10 +42,10 @@ def _server():
|
|
|
42
42
|
"""The server half, or one sentence saying how to get it."""
|
|
43
43
|
try:
|
|
44
44
|
from aiohttp import web
|
|
45
|
-
from .server import gateway, gw_http, pin, publish
|
|
45
|
+
from .server import commands, gateway, gw_http, pin, publish
|
|
46
46
|
except ImportError as e: # pragma: no cover
|
|
47
47
|
raise SystemExit(f"spatialdds.serve: {MISSING_SERVER}\n\n({e})")
|
|
48
|
-
return web, gateway, gw_http, pin, publish
|
|
48
|
+
return web, commands, gateway, gw_http, pin, publish
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def build_parser(ap: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
@@ -102,8 +102,33 @@ def build_parser(ap: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
|
102
102
|
return ap
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
def _demo_command(record: dict):
|
|
106
|
+
"""The `--demo` command handler: both of M.1's outcomes, no side effects.
|
|
107
|
+
|
|
108
|
+
A reference handler that pretended to act would be worse than none: it
|
|
109
|
+
would make the surface look wired to something. So this decides, says
|
|
110
|
+
why, and changes nothing --- which is all a demo registry can honestly do,
|
|
111
|
+
having no backend behind it.
|
|
112
|
+
|
|
113
|
+
`method == "DECLINE_ME"` is declined on purpose. Without a reachable
|
|
114
|
+
decline, the only exercised path would be acceptance, and M.1's whole
|
|
115
|
+
point is that a refusal is a first-class observable outcome; a surface
|
|
116
|
+
that can only say yes has not implemented it.
|
|
117
|
+
"""
|
|
118
|
+
from .server.commands import Outcome # the server half is optional
|
|
119
|
+
if record.get("method") == "DECLINE_ME":
|
|
120
|
+
return Outcome.decline(
|
|
121
|
+
"the demo handler declines this method by arrangement, so a "
|
|
122
|
+
"caller can see M.1's decline path without a backend: a refusal "
|
|
123
|
+
"carries a reason and is not an absence")
|
|
124
|
+
return Outcome.accept(
|
|
125
|
+
"accepted by the --demo handler, which records nothing: this registry "
|
|
126
|
+
"has no backend, and a handler that claimed to act would make the "
|
|
127
|
+
"surface look wired to something it is not")
|
|
128
|
+
|
|
129
|
+
|
|
105
130
|
def main(argv: list[str] | None = None) -> int:
|
|
106
|
-
web, gateway, gw_http, pin, publish = _server()
|
|
131
|
+
web, commands, gateway, gw_http, pin, publish = _server()
|
|
107
132
|
ap = build_parser(argparse.ArgumentParser(
|
|
108
133
|
prog="python -m spatialdds.serve",
|
|
109
134
|
description="Stand up a SpatialDDS registry."))
|
|
@@ -130,6 +155,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
130
155
|
if a.mcap is not None and not a.mcap.is_file():
|
|
131
156
|
raise SystemExit(f"no such recording: {a.mcap}")
|
|
132
157
|
|
|
158
|
+
# The N.6 demo handler. `--demo` exists so the surface is exercisable
|
|
159
|
+
# without a backend, and a command surface nobody can POST to is a route
|
|
160
|
+
# with no evidence behind it -- the conformance suite's first clause had
|
|
161
|
+
# never been exercised by any run in this program precisely because there
|
|
162
|
+
# was nothing to exercise it against.
|
|
163
|
+
#
|
|
164
|
+
# It accepts a `GeoAnchor` command and declines one verb, so both of M.1's
|
|
165
|
+
# outcomes are reachable from a bare `--demo`: acceptance, and a decline
|
|
166
|
+
# carrying a reason. It does nothing to the registry, because `--demo` has
|
|
167
|
+
# no backend to do anything with -- which is the honest shape for a
|
|
168
|
+
# reference handler and the reason its reason string says so.
|
|
169
|
+
cmd_registry = commands.Registry()
|
|
170
|
+
if a.demo:
|
|
171
|
+
cmd_registry.register("spatial.core.GeoAnchor", _demo_command)
|
|
172
|
+
|
|
133
173
|
token = a.token or secrets.token_urlsafe(24)
|
|
134
174
|
st = gateway.build_state(
|
|
135
175
|
a.mcap, a.manifests if a.manifests and a.manifests.is_dir() else None)
|
|
@@ -284,7 +324,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
284
324
|
try:
|
|
285
325
|
web.run_app(gw_http.make_app(st, base, token, a.publish, jr,
|
|
286
326
|
a.assets if a.assets
|
|
287
|
-
and a.assets.is_dir() else None
|
|
327
|
+
and a.assets.is_dir() else None,
|
|
328
|
+
command_registry=cmd_registry),
|
|
288
329
|
host=a.host, port=a.port, print=None)
|
|
289
330
|
except OSError as e:
|
|
290
331
|
# An already-bound port is the one failure where everything downstream
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""N.6 commands: the gateway owns the HTTP leg, a backend supplies the verbs.
|
|
2
|
+
|
|
3
|
+
Appendix N.6 is four sentences long and delegates its conventions to M.1, so
|
|
4
|
+
most of this module is the reasoning that fills the gap between them. The
|
|
5
|
+
ruling this implements: **N.6 is a gateway surface whose semantics a backend
|
|
6
|
+
supplies.** The gateway owns the route, the auth, the validation and the
|
|
7
|
+
response shape; a backend registers a handler per command type and decides
|
|
8
|
+
what a verb means. An endpoint whose backend registers nothing stays
|
|
9
|
+
northbound-only, and its POST stays exactly as conformant as it was before
|
|
10
|
+
this module existed.
|
|
11
|
+
|
|
12
|
+
**Why there is a seam at all.** The first consumer of this surface is
|
|
13
|
+
`SpatialDDS-demo`'s web bridge, which embeds the gateway against a live DDS
|
|
14
|
+
bus: a `goto` arriving as a POST has to leave as a keyed `ModelCommand` on a
|
|
15
|
+
DDS topic, and nothing in this repository knows how to do that or should. So
|
|
16
|
+
the deliverable is not only the route but the boundary — the narrowest thing
|
|
17
|
+
an embedder can implement to own the verbs without owning the HTTP.
|
|
18
|
+
|
|
19
|
+
**What N.6 fixes and what it leaves open.** It fixes that a command is a POST,
|
|
20
|
+
that the response carries the outcome *including an explicit decline*, and
|
|
21
|
+
that an endpoint without a command lane refuses with `405` or `404` and never
|
|
22
|
+
accepts a command it cannot act on. It says nothing about status codes for the
|
|
23
|
+
outcomes themselves, nothing about how a client discovers that commands are
|
|
24
|
+
accepted, and nothing about completion tracking. Those are decided here and
|
|
25
|
+
documented in `docs/WIRE.md`; none of them is invented where the text speaks.
|
|
26
|
+
|
|
27
|
+
**M.1's vocabulary, which N.6 points at.** A decline is "a first-class,
|
|
28
|
+
observable outcome, not an absence", carrying a terminal state and a
|
|
29
|
+
human-readable reason. The value of the pattern is that an observer can
|
|
30
|
+
distinguish *refused* from *not-yet-answered* from *not-for-me*. That is why
|
|
31
|
+
`Outcome` carries a reason on the decline path and why a declined command is
|
|
32
|
+
not reported with a success status: a caller that checks only the status must
|
|
33
|
+
not read a refusal as an acceptance, which would turn M.1's first-class
|
|
34
|
+
outcome back into the absence it exists to replace.
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from dataclasses import dataclass
|
|
39
|
+
from typing import Any, Callable
|
|
40
|
+
|
|
41
|
+
#: The manifest member that advertises a lane as accepting commands.
|
|
42
|
+
#:
|
|
43
|
+
#: **Implementation-defined, and flagged as a specification gap.** N.7's
|
|
44
|
+
#: topic-entry table is a MUST-carry list of six fields plus two optional
|
|
45
|
+
#: ones, and none of them can say "this lane accepts commands" --- while N.6
|
|
46
|
+
#: says "where an endpoint exposes a command surface" and gives a client no
|
|
47
|
+
#: way to find out whether it does. A client would otherwise have to POST and
|
|
48
|
+
#: read the refusal, which is discovery by trial.
|
|
49
|
+
#:
|
|
50
|
+
#: So this member exists, under this repository's own name, documented in
|
|
51
|
+
#: `docs/WIRE.md`, and filed for 1.9 rather than treated as settled. N.7's
|
|
52
|
+
#: list is a floor rather than a ceiling, so carrying an extra member is
|
|
53
|
+
#: conformant; a *client* may not depend on it until the specification adopts
|
|
54
|
+
#: one, and nothing in this repository reads it except the manifest's own
|
|
55
|
+
#: reader and the conformance suite.
|
|
56
|
+
ADVERTISEMENT = "accepts_commands"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class Outcome:
|
|
61
|
+
"""What a backend did with a command.
|
|
62
|
+
|
|
63
|
+
Two states and a reason, which is M.1's vocabulary and not a richer one:
|
|
64
|
+
there is no `PENDING` because N.6 requires no completion tracking and
|
|
65
|
+
inventing it would put this gateway ahead of the specification it
|
|
66
|
+
implements. A backend that needs asynchronous completion reports
|
|
67
|
+
acceptance here and publishes the completion on its own lane, which is
|
|
68
|
+
what the DDS side already does.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
accepted: bool
|
|
72
|
+
#: Required on a decline, and M.1 is why: "a decline is a first-class,
|
|
73
|
+
#: observable outcome, not an absence." A refusal with no reason is an
|
|
74
|
+
#: absence with a status code.
|
|
75
|
+
reason: str = ""
|
|
76
|
+
|
|
77
|
+
def __post_init__(self) -> None:
|
|
78
|
+
if not self.accepted and not self.reason:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"a declined command must carry a reason: M.1 makes a decline "
|
|
81
|
+
"a first-class outcome, and a refusal nobody can read is the "
|
|
82
|
+
"absence it exists to replace")
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def accept(cls, reason: str = "") -> "Outcome":
|
|
86
|
+
return cls(True, reason)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def decline(cls, reason: str) -> "Outcome":
|
|
90
|
+
return cls(False, reason)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
#: A handler receives the validated, lifted record --- the same JSON shape a
|
|
94
|
+
#: GET of that type would serve, after N.1 canonicalization and schema
|
|
95
|
+
#: validation --- and returns an `Outcome`. It never sees the request, the
|
|
96
|
+
#: token or the socket: an embedder owns verbs, not HTTP.
|
|
97
|
+
Handler = Callable[[dict[str, Any]], Outcome]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Registry:
|
|
101
|
+
"""The handlers an embedder has registered, keyed by type FQN.
|
|
102
|
+
|
|
103
|
+
Keyed by FQN rather than by topic so that a backend which accepts
|
|
104
|
+
`spatial.owm.ModelCommand` accepts it wherever a scene advertises that
|
|
105
|
+
type, which is how every other type-keyed table in this package works
|
|
106
|
+
(`DURABILITY`, `PUBLISHABLE`, the schema index).
|
|
107
|
+
|
|
108
|
+
Empty is the normal state. A gateway serving a recording has no verbs to
|
|
109
|
+
offer, and N.6 says a conformance suite MUST NOT require a command
|
|
110
|
+
surface to exist.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self) -> None:
|
|
114
|
+
self._handlers: dict[str, Handler] = {}
|
|
115
|
+
|
|
116
|
+
def register(self, fqn: str, handler: Handler) -> None:
|
|
117
|
+
"""Take commands of one type. Re-registering replaces, deliberately:
|
|
118
|
+
an embedder reconfiguring a lane should not have to tear down."""
|
|
119
|
+
self._handlers[fqn] = handler
|
|
120
|
+
|
|
121
|
+
def accepts(self, fqn: str) -> bool:
|
|
122
|
+
return fqn in self._handlers
|
|
123
|
+
|
|
124
|
+
def handler(self, fqn: str) -> Handler | None:
|
|
125
|
+
return self._handlers.get(fqn)
|
|
126
|
+
|
|
127
|
+
def types(self) -> list[str]:
|
|
128
|
+
return sorted(self._handlers)
|
|
129
|
+
|
|
130
|
+
def __bool__(self) -> bool:
|
|
131
|
+
return bool(self._handlers)
|
|
@@ -31,11 +31,11 @@ import json
|
|
|
31
31
|
import math
|
|
32
32
|
from dataclasses import dataclass, field
|
|
33
33
|
from pathlib import Path
|
|
34
|
-
from typing import Any, Iterable
|
|
34
|
+
from typing import Any, Callable, Iterable
|
|
35
35
|
|
|
36
36
|
from mcap.reader import make_reader
|
|
37
37
|
|
|
38
|
-
from . import idlmodel, pin, webjson, xcdr2
|
|
38
|
+
from . import commands, idlmodel, pin, webjson, xcdr2
|
|
39
39
|
|
|
40
40
|
PUBLIC = "public-cacheable"
|
|
41
41
|
OPERATOR = "operator-scoped"
|
|
@@ -553,7 +553,9 @@ def declared_service(st: State, scene: str) -> dict | None:
|
|
|
553
553
|
|
|
554
554
|
|
|
555
555
|
def service_manifest(st: State, scene: str, base: str,
|
|
556
|
-
declared: dict | None = None
|
|
556
|
+
declared: dict | None = None,
|
|
557
|
+
accepts_commands: Callable[[str], bool] | None = None
|
|
558
|
+
) -> dict:
|
|
557
559
|
"""The §8.2.3 service manifest for one scene.
|
|
558
560
|
|
|
559
561
|
`service.connection` is deliberately absent: it carries DDS connection
|
|
@@ -587,6 +589,21 @@ def service_manifest(st: State, scene: str, base: str,
|
|
|
587
589
|
"access": res.access,
|
|
588
590
|
"url": base + path,
|
|
589
591
|
})
|
|
592
|
+
# N.6 asks a client to POST a command to the lane that accepts one,
|
|
593
|
+
# and N.7's topic entry has no member that says which lane that is:
|
|
594
|
+
# its table is six MUST-carry fields plus `qos_profile` and
|
|
595
|
+
# `liveliness`, none of which can carry "verbs welcome here". Without
|
|
596
|
+
# something, a client discovers the command surface by POSTing and
|
|
597
|
+
# reading the refusal, which is discovery by trial.
|
|
598
|
+
#
|
|
599
|
+
# So this member is added, under this repository's own name, and
|
|
600
|
+
# filed as a specification gap rather than treated as settled -- see
|
|
601
|
+
# docs/WIRE.md and the 1.9 candidates entry. N.7's list is a floor
|
|
602
|
+
# and not a ceiling, so an extra member is conformant; it is omitted
|
|
603
|
+
# entirely rather than written `false`, so a manifest from an endpoint
|
|
604
|
+
# with no verbs is byte-identical to one from before this existed.
|
|
605
|
+
if accepts_commands and accepts_commands(res.fqn):
|
|
606
|
+
topics_block[-1][commands.ADVERTISEMENT] = True
|
|
590
607
|
man: dict[str, Any] = {
|
|
591
608
|
"id": f"spatialdds://spatialdds-web.local/{scene}/service/gateway",
|
|
592
609
|
"profile": "spatial.manifest/1.8",
|
|
@@ -659,7 +676,8 @@ def service_manifest(st: State, scene: str, base: str,
|
|
|
659
676
|
|
|
660
677
|
def search(st: State, base: str, geohash: str | None = None,
|
|
661
678
|
kinds: list[str] | None = None,
|
|
662
|
-
coverage: list[dict] | None = None
|
|
679
|
+
coverage: list[dict] | None = None,
|
|
680
|
+
accepts_commands: Callable[[str], bool] | None = None) -> dict:
|
|
663
681
|
"""The §3.3.0 search response: `{"results": [...], "next_page_token": ""}`."""
|
|
664
682
|
wanted: list[tuple[float, float, float, float]] = []
|
|
665
683
|
if geohash:
|
|
@@ -679,10 +697,12 @@ def search(st: State, base: str, geohash: str | None = None,
|
|
|
679
697
|
for scene in st.scenes:
|
|
680
698
|
declared = declared_services(st, scene)
|
|
681
699
|
if declared:
|
|
682
|
-
manifests.extend(service_manifest(st, scene, base, d
|
|
700
|
+
manifests.extend(service_manifest(st, scene, base, d,
|
|
701
|
+
accepts_commands)
|
|
683
702
|
for d in declared)
|
|
684
703
|
else:
|
|
685
|
-
manifests.append(service_manifest(st, scene, base
|
|
704
|
+
manifests.append(service_manifest(st, scene, base,
|
|
705
|
+
accepts_commands=accepts_commands))
|
|
686
706
|
for man in manifests:
|
|
687
707
|
if kinds and man["service"]["kind"] not in kinds:
|
|
688
708
|
continue
|