astrox-python 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,138 @@
1
+ """Constraint component value objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass
7
+ from typing import Any, TypeAlias
8
+
9
+ from ._common import _include_if_supplied, _number_sequence_to_list
10
+
11
+ @dataclass(frozen=True, kw_only=True)
12
+ class ElevationConstraint:
13
+ """Elevation-angle constraint fragment shared by ASTROX entity and coverage inputs."""
14
+
15
+ minimum_deg: float | None = None
16
+ maximum_deg: float | None = None
17
+ maximum_enabled: bool | None = None
18
+ text: str | None = None
19
+
20
+ def to_wire(self) -> dict[str, Any]:
21
+ """Lower to an ASTROX elevation-angle constraint fragment."""
22
+ payload: dict[str, Any] = {"$type": "ElevationAngle"}
23
+ _include_if_supplied(payload, "MinimumValue", self.minimum_deg)
24
+ _include_if_supplied(payload, "MaximumValue", self.maximum_deg)
25
+ _include_if_supplied(payload, "IsMaximumEnabled", self.maximum_enabled)
26
+ _include_if_supplied(payload, "Text", self.text)
27
+ return payload
28
+
29
+
30
+ @dataclass(frozen=True, kw_only=True)
31
+ class RangeConstraint:
32
+ """Range constraint fragment shared by ASTROX entity and coverage inputs."""
33
+
34
+ minimum_km: float | None = None
35
+ maximum_km: float | None = None
36
+ maximum_enabled: bool | None = None
37
+ text: str | None = None
38
+
39
+ def to_wire(self) -> dict[str, Any]:
40
+ """Lower to an ASTROX range constraint fragment."""
41
+ payload: dict[str, Any] = {"$type": "Range"}
42
+ _include_if_supplied(payload, "MinimumValue", self.minimum_km)
43
+ _include_if_supplied(payload, "MaximumValue", self.maximum_km)
44
+ _include_if_supplied(payload, "IsMaximumEnabled", self.maximum_enabled)
45
+ _include_if_supplied(payload, "Text", self.text)
46
+ return payload
47
+
48
+
49
+ @dataclass(frozen=True, kw_only=True)
50
+ class AzElMaskConstraint:
51
+ """Azimuth/elevation mask constraint fragment shared by ASTROX entity and coverage inputs."""
52
+
53
+ az_el_mask_rad: tuple[float, ...]
54
+ max_range_km: float | None = None
55
+ text: str | None = None
56
+
57
+ def to_wire(self) -> dict[str, Any]:
58
+ """Lower to an ASTROX azimuth/elevation mask constraint fragment."""
59
+ payload: dict[str, Any] = {
60
+ "$type": "AzElMask",
61
+ "AzElMaskData": list(self.az_el_mask_rad),
62
+ }
63
+ _include_if_supplied(payload, "MaxRange", self.max_range_km)
64
+ _include_if_supplied(payload, "Text", self.text)
65
+ return payload
66
+
67
+
68
+ Constraint: TypeAlias = ElevationConstraint | RangeConstraint | AzElMaskConstraint
69
+
70
+
71
+ _CONSTRAINT_TYPES = (ElevationConstraint, RangeConstraint, AzElMaskConstraint)
72
+
73
+
74
+ def elevation_constraint(
75
+ *,
76
+ minimum_deg: float | None = None,
77
+ maximum_deg: float | None = None,
78
+ maximum_enabled: bool | None = None,
79
+ text: str | None = None,
80
+ ) -> ElevationConstraint:
81
+ """Create an elevation-angle constraint fragment."""
82
+ return ElevationConstraint(
83
+ minimum_deg=minimum_deg,
84
+ maximum_deg=maximum_deg,
85
+ maximum_enabled=maximum_enabled,
86
+ text=text,
87
+ )
88
+
89
+
90
+ def range_constraint(
91
+ *,
92
+ minimum_km: float | None = None,
93
+ maximum_km: float | None = None,
94
+ maximum_enabled: bool | None = None,
95
+ text: str | None = None,
96
+ ) -> RangeConstraint:
97
+ """Create a range constraint fragment."""
98
+ return RangeConstraint(
99
+ minimum_km=minimum_km,
100
+ maximum_km=maximum_km,
101
+ maximum_enabled=maximum_enabled,
102
+ text=text,
103
+ )
104
+
105
+
106
+ def az_el_mask_constraint(
107
+ *,
108
+ az_el_mask_rad: Sequence[float],
109
+ max_range_km: float | None = None,
110
+ text: str | None = None,
111
+ ) -> AzElMaskConstraint:
112
+ """Create an azimuth/elevation mask constraint fragment.
113
+
114
+ ``az_el_mask_rad`` is a flat sequence of alternating azimuth and elevation
115
+ samples in radians. ASTROX interpolates the mask piecewise-linearly in
116
+ azimuth and applies it at the constrained participant. Live validation shows
117
+ this constraint is only meaningful for ``SitePosition`` participants; the
118
+ server rejects it for moving position sources.
119
+
120
+ ``max_range_km`` is forwarded but is not enforced by ASTROX. The live
121
+ OpenAPI description and server behavior both treat it as documentation-only.
122
+ """
123
+ return AzElMaskConstraint(
124
+ az_el_mask_rad=tuple(
125
+ _number_sequence_to_list(
126
+ az_el_mask_rad,
127
+ parameter="az_el_mask_rad",
128
+ )
129
+ ),
130
+ max_range_km=max_range_km,
131
+ text=text,
132
+ )
133
+
134
+
135
+ def _constraint_to_wire(constraint: Constraint) -> dict[str, Any]:
136
+ if not isinstance(constraint, _CONSTRAINT_TYPES):
137
+ raise TypeError("constraint must be an astrox.components constraint value")
138
+ return constraint.to_wire()
@@ -0,0 +1,167 @@
1
+ """Named ASTROX analysis object components."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from ._axes import EntityAxes, _AXES_TYPES, _axes_to_wire
10
+ from ._common import (
11
+ _include_if_supplied,
12
+ _typed_tuple,
13
+ _validate_group_restriction,
14
+ )
15
+ from ._constraints import Constraint, _CONSTRAINT_TYPES, _constraint_to_wire
16
+ from ._positions import EntityPosition, _POSITION_TYPES, _position_to_wire
17
+ from ._sensors import (
18
+ EntitySensor,
19
+ SensorPointing,
20
+ _SENSOR_POINTING_TYPES,
21
+ _SENSOR_TYPES,
22
+ _sensor_pointing_to_wire,
23
+ _sensor_to_wire,
24
+ )
25
+ from ._vgt import VgtProvider, _vgt_to_wire
26
+
27
+ def _entities_to_tuple(
28
+ values: Sequence[Entity],
29
+ *,
30
+ parameter: str,
31
+ ) -> tuple[Entity, ...]:
32
+ if isinstance(values, (str, bytes)) or not isinstance(values, Sequence):
33
+ raise TypeError(f"{parameter} must be a sequence of Entity values")
34
+ items = tuple(values)
35
+ if not all(isinstance(item, Entity) for item in items):
36
+ raise TypeError(f"{parameter} must be a sequence of Entity values")
37
+ return items
38
+
39
+
40
+ @dataclass(frozen=True, kw_only=True)
41
+ class Entity:
42
+ """Named ASTROX analysis object composed from a position source and metadata."""
43
+
44
+ name: str
45
+ position: EntityPosition
46
+ description: str | None = None
47
+ vgt: VgtProvider | None = None
48
+ orientation: EntityAxes | None = None
49
+ sensor: EntitySensor | None = None
50
+ sensor_pointing: SensorPointing | None = None
51
+ constraints: tuple[Constraint, ...] | None = None
52
+
53
+ def to_wire(self) -> dict[str, Any]:
54
+ """Lower to the common ASTROX entity body."""
55
+ payload: dict[str, Any] = {
56
+ "Name": self.name,
57
+ }
58
+ _include_if_supplied(payload, "Description", self.description)
59
+ if self.vgt is not None:
60
+ payload["Vgt"] = _vgt_to_wire(self.vgt)
61
+ payload["Position"] = _position_to_wire(self.position)
62
+ if self.orientation is not None:
63
+ payload["Orientation"] = _axes_to_wire(self.orientation)
64
+ if self.sensor is not None:
65
+ payload["Sensor"] = _sensor_to_wire(self.sensor)
66
+ if self.sensor_pointing is not None:
67
+ payload["SensorPointing"] = _sensor_pointing_to_wire(self.sensor_pointing)
68
+ if self.constraints is not None:
69
+ payload["Constraints"] = [
70
+ _constraint_to_wire(constraint) for constraint in self.constraints
71
+ ]
72
+ return payload
73
+
74
+
75
+ @dataclass(frozen=True, kw_only=True)
76
+ class EntityGroup:
77
+ """Named entity group for workflows that accept grouped entities."""
78
+
79
+ name: str
80
+ members: tuple[Entity, ...]
81
+ from_restriction: str | None = None
82
+ from_number: int | None = None
83
+ to_restriction: str | None = None
84
+ to_number: int | None = None
85
+
86
+ def to_wire(self) -> dict[str, Any]:
87
+ """Lower to the ASTROX EntityPathGroup fragment."""
88
+ payload: dict[str, Any] = {
89
+ "$type": "EntityPathGroup",
90
+ "Name": self.name,
91
+ "AssignedObjects": [member.to_wire() for member in self.members],
92
+ }
93
+ _include_if_supplied(payload, "FromAccess_Restriction", self.from_restriction)
94
+ _include_if_supplied(payload, "FromAccess_Number", self.from_number)
95
+ _include_if_supplied(payload, "ToAccess_Restriction", self.to_restriction)
96
+ _include_if_supplied(payload, "ToAccess_Number", self.to_number)
97
+ return payload
98
+
99
+
100
+ def entity(
101
+ *,
102
+ name: str,
103
+ position: EntityPosition,
104
+ description: str | None = None,
105
+ vgt: VgtProvider | None = None,
106
+ orientation: EntityAxes | None = None,
107
+ sensor: EntitySensor | None = None,
108
+ sensor_pointing: SensorPointing | None = None,
109
+ constraints: Sequence[Constraint] | None = None,
110
+ ) -> Entity:
111
+ """Create a named ASTROX analysis object."""
112
+ if not isinstance(position, _POSITION_TYPES):
113
+ raise TypeError("position must be an astrox.components position value")
114
+ if vgt is not None and not isinstance(vgt, VgtProvider):
115
+ raise TypeError("vgt must be an astrox.components.VgtProvider value")
116
+ if orientation is not None and not isinstance(orientation, _AXES_TYPES):
117
+ raise TypeError("orientation must be an astrox.components axes value")
118
+ if sensor is not None and not isinstance(sensor, _SENSOR_TYPES):
119
+ raise TypeError("sensor must be an astrox.components sensor value")
120
+ if sensor_pointing is not None and not isinstance(
121
+ sensor_pointing,
122
+ _SENSOR_POINTING_TYPES,
123
+ ):
124
+ raise TypeError(
125
+ "sensor_pointing must be an astrox.components sensor-pointing value"
126
+ )
127
+ constraint_items = (
128
+ _typed_tuple(constraints, _CONSTRAINT_TYPES, parameter="constraints")
129
+ if constraints is not None
130
+ else None
131
+ )
132
+ return Entity(
133
+ name=name,
134
+ position=position,
135
+ description=description,
136
+ vgt=vgt,
137
+ orientation=orientation,
138
+ sensor=sensor,
139
+ sensor_pointing=sensor_pointing,
140
+ constraints=constraint_items,
141
+ )
142
+
143
+
144
+ def entity_group(
145
+ *,
146
+ name: str,
147
+ members: Sequence[Entity],
148
+ from_restriction: str | None = None,
149
+ from_number: int | None = None,
150
+ to_restriction: str | None = None,
151
+ to_number: int | None = None,
152
+ ) -> EntityGroup:
153
+ """Create a named entity group."""
154
+ return EntityGroup(
155
+ name=name,
156
+ members=_entities_to_tuple(members, parameter="members"),
157
+ from_restriction=_validate_group_restriction(
158
+ from_restriction,
159
+ parameter="from_restriction",
160
+ ),
161
+ from_number=from_number,
162
+ to_restriction=_validate_group_restriction(
163
+ to_restriction,
164
+ parameter="to_restriction",
165
+ ),
166
+ to_number=to_number,
167
+ )