orbsim-sdk 0.1.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.
- orbsim_sdk-0.1.0/PKG-INFO +61 -0
- orbsim_sdk-0.1.0/README.md +52 -0
- orbsim_sdk-0.1.0/pyproject.toml +20 -0
- orbsim_sdk-0.1.0/setup.cfg +4 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk/__init__.py +93 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk/client.py +393 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk/exceptions.py +9 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk/models.py +317 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk.egg-info/PKG-INFO +61 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk.egg-info/SOURCES.txt +11 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk.egg-info/dependency_links.txt +1 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk.egg-info/requires.txt +2 -0
- orbsim_sdk-0.1.0/src/orbsim_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orbsim-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the OrbSim API
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: pydantic>=2.8.0
|
|
9
|
+
|
|
10
|
+
# OrbSim SDK
|
|
11
|
+
|
|
12
|
+
Typed Python client for the OrbSim API.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install /path/to/orbsim/sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from orbsim_sdk import OrbitSource, OrbsimClient, SatelliteCreate, TLEDefinition
|
|
24
|
+
|
|
25
|
+
client = OrbsimClient("http://127.0.0.1:8000/api")
|
|
26
|
+
auth = client.login("admin@example.com", "supersecret123", token_name="SDK Session")
|
|
27
|
+
projects = client.list_projects()
|
|
28
|
+
|
|
29
|
+
satellite = client.create_satellite(
|
|
30
|
+
projects[0].id,
|
|
31
|
+
SatelliteCreate(
|
|
32
|
+
name="ISS Copy",
|
|
33
|
+
source=OrbitSource.TLE,
|
|
34
|
+
tle=TLEDefinition(
|
|
35
|
+
line1="1 25544U 98067A 26099.50000000 .00016717 00000+0 30747-3 0 9991",
|
|
36
|
+
line2="2 25544 51.6400 54.1200 0003870 123.4567 321.6543 15.50000000123456",
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
state = client.get_satellite_state(projects[0].id, satellite.id, "2026-04-09T12:00:00Z")
|
|
42
|
+
print(state.latitude_deg, state.longitude_deg, state.altitude_m)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Async Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
|
|
50
|
+
from orbsim_sdk import AsyncOrbsimClient
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def main():
|
|
54
|
+
async with AsyncOrbsimClient("http://127.0.0.1:8000/api") as client:
|
|
55
|
+
await client.login("admin@example.com", "supersecret123", token_name="Async SDK Session")
|
|
56
|
+
projects = await client.list_projects()
|
|
57
|
+
print([project.name for project in projects])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# OrbSim SDK
|
|
2
|
+
|
|
3
|
+
Typed Python client for the OrbSim API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install /path/to/orbsim/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from orbsim_sdk import OrbitSource, OrbsimClient, SatelliteCreate, TLEDefinition
|
|
15
|
+
|
|
16
|
+
client = OrbsimClient("http://127.0.0.1:8000/api")
|
|
17
|
+
auth = client.login("admin@example.com", "supersecret123", token_name="SDK Session")
|
|
18
|
+
projects = client.list_projects()
|
|
19
|
+
|
|
20
|
+
satellite = client.create_satellite(
|
|
21
|
+
projects[0].id,
|
|
22
|
+
SatelliteCreate(
|
|
23
|
+
name="ISS Copy",
|
|
24
|
+
source=OrbitSource.TLE,
|
|
25
|
+
tle=TLEDefinition(
|
|
26
|
+
line1="1 25544U 98067A 26099.50000000 .00016717 00000+0 30747-3 0 9991",
|
|
27
|
+
line2="2 25544 51.6400 54.1200 0003870 123.4567 321.6543 15.50000000123456",
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
state = client.get_satellite_state(projects[0].id, satellite.id, "2026-04-09T12:00:00Z")
|
|
33
|
+
print(state.latitude_deg, state.longitude_deg, state.altitude_m)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Async Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
|
|
41
|
+
from orbsim_sdk import AsyncOrbsimClient
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
async with AsyncOrbsimClient("http://127.0.0.1:8000/api") as client:
|
|
46
|
+
await client.login("admin@example.com", "supersecret123", token_name="Async SDK Session")
|
|
47
|
+
projects = await client.list_projects()
|
|
48
|
+
print([project.name for project in projects])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
asyncio.run(main())
|
|
52
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "orbsim-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the OrbSim API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.27.0",
|
|
13
|
+
"pydantic>=2.8.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
package-dir = {"" = "src"}
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from .client import AsyncOrbsimClient, OrbsimClient
|
|
2
|
+
from .exceptions import OrbsimAPIError, OrbsimSDKError
|
|
3
|
+
from .models import (
|
|
4
|
+
AdminDashboard,
|
|
5
|
+
AdminProjectInfo,
|
|
6
|
+
AnomalyKind,
|
|
7
|
+
AttitudeMode,
|
|
8
|
+
AttitudeSample,
|
|
9
|
+
AuthTokenResponse,
|
|
10
|
+
BootstrapAdminRequest,
|
|
11
|
+
CartesianState,
|
|
12
|
+
Constellation,
|
|
13
|
+
ConstellationCreate,
|
|
14
|
+
ConstellationDesignCreate,
|
|
15
|
+
ConstellationDesignResponse,
|
|
16
|
+
ConstellationDesignType,
|
|
17
|
+
ContactInterval,
|
|
18
|
+
GroundStation,
|
|
19
|
+
GroundStationCreate,
|
|
20
|
+
HealthResponse,
|
|
21
|
+
KeplerianDefinition,
|
|
22
|
+
LinkBudgetRequest,
|
|
23
|
+
LinkBudgetResponse,
|
|
24
|
+
LinkKind,
|
|
25
|
+
LoginRequest,
|
|
26
|
+
NetworkResponse,
|
|
27
|
+
OrbitalStateResponse,
|
|
28
|
+
OrbitSource,
|
|
29
|
+
Project,
|
|
30
|
+
ProjectCreate,
|
|
31
|
+
PublicContactQuery,
|
|
32
|
+
PublicSatelliteQuery,
|
|
33
|
+
PublicTLE,
|
|
34
|
+
RegisterRequest,
|
|
35
|
+
Satellite,
|
|
36
|
+
SatelliteCreate,
|
|
37
|
+
SatelliteSubsystemState,
|
|
38
|
+
TLEDefinition,
|
|
39
|
+
TimeRangeQuery,
|
|
40
|
+
TokenCreateRequest,
|
|
41
|
+
TokenCreateResponse,
|
|
42
|
+
TokenInfo,
|
|
43
|
+
UserProfile,
|
|
44
|
+
WalkerDeltaDefinition,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"AdminDashboard",
|
|
49
|
+
"AdminProjectInfo",
|
|
50
|
+
"AnomalyKind",
|
|
51
|
+
"AttitudeMode",
|
|
52
|
+
"AttitudeSample",
|
|
53
|
+
"AuthTokenResponse",
|
|
54
|
+
"BootstrapAdminRequest",
|
|
55
|
+
"CartesianState",
|
|
56
|
+
"Constellation",
|
|
57
|
+
"ConstellationCreate",
|
|
58
|
+
"ConstellationDesignCreate",
|
|
59
|
+
"ConstellationDesignResponse",
|
|
60
|
+
"ConstellationDesignType",
|
|
61
|
+
"ContactInterval",
|
|
62
|
+
"GroundStation",
|
|
63
|
+
"GroundStationCreate",
|
|
64
|
+
"HealthResponse",
|
|
65
|
+
"KeplerianDefinition",
|
|
66
|
+
"LinkBudgetRequest",
|
|
67
|
+
"LinkBudgetResponse",
|
|
68
|
+
"LinkKind",
|
|
69
|
+
"LoginRequest",
|
|
70
|
+
"NetworkResponse",
|
|
71
|
+
"OrbitalStateResponse",
|
|
72
|
+
"OrbitSource",
|
|
73
|
+
"AsyncOrbsimClient",
|
|
74
|
+
"OrbsimAPIError",
|
|
75
|
+
"OrbsimClient",
|
|
76
|
+
"OrbsimSDKError",
|
|
77
|
+
"Project",
|
|
78
|
+
"ProjectCreate",
|
|
79
|
+
"PublicContactQuery",
|
|
80
|
+
"PublicSatelliteQuery",
|
|
81
|
+
"PublicTLE",
|
|
82
|
+
"RegisterRequest",
|
|
83
|
+
"Satellite",
|
|
84
|
+
"SatelliteCreate",
|
|
85
|
+
"SatelliteSubsystemState",
|
|
86
|
+
"TLEDefinition",
|
|
87
|
+
"TimeRangeQuery",
|
|
88
|
+
"TokenCreateRequest",
|
|
89
|
+
"TokenCreateResponse",
|
|
90
|
+
"TokenInfo",
|
|
91
|
+
"UserProfile",
|
|
92
|
+
"WalkerDeltaDefinition",
|
|
93
|
+
]
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from .exceptions import OrbsimAPIError
|
|
10
|
+
from .models import (
|
|
11
|
+
AdminDashboard,
|
|
12
|
+
AdminProjectInfo,
|
|
13
|
+
AttitudeSample,
|
|
14
|
+
AuthTokenResponse,
|
|
15
|
+
BootstrapAdminRequest,
|
|
16
|
+
Constellation,
|
|
17
|
+
ConstellationCreate,
|
|
18
|
+
ConstellationDesignCreate,
|
|
19
|
+
ConstellationDesignResponse,
|
|
20
|
+
ContactInterval,
|
|
21
|
+
GroundStation,
|
|
22
|
+
GroundStationCreate,
|
|
23
|
+
HealthResponse,
|
|
24
|
+
LinkBudgetRequest,
|
|
25
|
+
LinkBudgetResponse,
|
|
26
|
+
LoginRequest,
|
|
27
|
+
NetworkResponse,
|
|
28
|
+
OrbitalStateResponse,
|
|
29
|
+
Project,
|
|
30
|
+
ProjectCreate,
|
|
31
|
+
PublicContactQuery,
|
|
32
|
+
PublicSatelliteQuery,
|
|
33
|
+
PublicTLE,
|
|
34
|
+
RegisterRequest,
|
|
35
|
+
Satellite,
|
|
36
|
+
SatelliteCreate,
|
|
37
|
+
SatelliteSubsystemState,
|
|
38
|
+
TimeRangeQuery,
|
|
39
|
+
TokenCreateRequest,
|
|
40
|
+
TokenCreateResponse,
|
|
41
|
+
TokenInfo,
|
|
42
|
+
UserProfile,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _OrbsimClientBase:
|
|
49
|
+
def _model(self, model_type: type[ModelT], data: Any) -> ModelT:
|
|
50
|
+
return model_type.model_validate(data)
|
|
51
|
+
|
|
52
|
+
def _models(self, model_type: type[ModelT], data: list[Any]) -> list[ModelT]:
|
|
53
|
+
return [self._model(model_type, item) for item in data]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class OrbsimClient(_OrbsimClientBase):
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
base_url: str,
|
|
60
|
+
token: str | None = None,
|
|
61
|
+
timeout: float = 30.0,
|
|
62
|
+
client: httpx.Client | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
self.base_url = base_url.rstrip("/")
|
|
65
|
+
self._client = client or httpx.Client(base_url=self.base_url, timeout=timeout)
|
|
66
|
+
self._owns_client = client is None
|
|
67
|
+
if token:
|
|
68
|
+
self.set_token(token)
|
|
69
|
+
|
|
70
|
+
def close(self) -> None:
|
|
71
|
+
if self._owns_client:
|
|
72
|
+
self._client.close()
|
|
73
|
+
|
|
74
|
+
def __enter__(self) -> "OrbsimClient":
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
78
|
+
self.close()
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def token(self) -> str | None:
|
|
82
|
+
auth = self._client.headers.get("Authorization", "")
|
|
83
|
+
if auth.startswith("Bearer "):
|
|
84
|
+
return auth.removeprefix("Bearer ")
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def set_token(self, token: str | None) -> None:
|
|
88
|
+
if token:
|
|
89
|
+
self._client.headers["Authorization"] = f"Bearer {token}"
|
|
90
|
+
else:
|
|
91
|
+
self._client.headers.pop("Authorization", None)
|
|
92
|
+
|
|
93
|
+
def _request(self, method: str, path: str, *, params: dict[str, Any] | None = None, payload: BaseModel | dict[str, Any] | None = None) -> Any:
|
|
94
|
+
json_payload: dict[str, Any] | None = None
|
|
95
|
+
if payload is not None:
|
|
96
|
+
json_payload = payload.model_dump(mode="json", exclude_none=True) if isinstance(payload, BaseModel) else payload
|
|
97
|
+
response = self._client.request(method, path, params=params, json=json_payload)
|
|
98
|
+
if response.is_error:
|
|
99
|
+
try:
|
|
100
|
+
detail = response.json().get("detail", response.text)
|
|
101
|
+
except Exception:
|
|
102
|
+
detail = response.text
|
|
103
|
+
raise OrbsimAPIError(response.status_code, detail)
|
|
104
|
+
if not response.content:
|
|
105
|
+
return None
|
|
106
|
+
return response.json()
|
|
107
|
+
|
|
108
|
+
def bootstrap_admin(self, email: str, password: str, full_name: str) -> AuthTokenResponse:
|
|
109
|
+
result = self._model(AuthTokenResponse, self._request("POST", "/auth/bootstrap-admin", payload=BootstrapAdminRequest(email=email, password=password, full_name=full_name)))
|
|
110
|
+
self.set_token(result.token)
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
def register(self, email: str, password: str, full_name: str) -> AuthTokenResponse:
|
|
114
|
+
result = self._model(AuthTokenResponse, self._request("POST", "/auth/register", payload=RegisterRequest(email=email, password=password, full_name=full_name)))
|
|
115
|
+
self.set_token(result.token)
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
def login(self, email: str, password: str, token_name: str = "Default API Token") -> AuthTokenResponse:
|
|
119
|
+
result = self._model(AuthTokenResponse, self._request("POST", "/auth/login", payload=LoginRequest(email=email, password=password, token_name=token_name)))
|
|
120
|
+
self.set_token(result.token)
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
def me(self) -> UserProfile:
|
|
124
|
+
return self._model(UserProfile, self._request("GET", "/auth/me"))
|
|
125
|
+
|
|
126
|
+
def list_tokens(self) -> list[TokenInfo]:
|
|
127
|
+
return self._models(TokenInfo, self._request("GET", "/auth/tokens"))
|
|
128
|
+
|
|
129
|
+
def create_token(self, name: str) -> TokenCreateResponse:
|
|
130
|
+
return self._model(TokenCreateResponse, self._request("POST", "/auth/tokens", payload=TokenCreateRequest(name=name)))
|
|
131
|
+
|
|
132
|
+
def revoke_own_token(self, token_id: str) -> TokenInfo:
|
|
133
|
+
return self._model(TokenInfo, self._request("DELETE", f"/auth/tokens/{token_id}"))
|
|
134
|
+
|
|
135
|
+
def health(self) -> HealthResponse:
|
|
136
|
+
return self._model(HealthResponse, self._request("GET", "/health"))
|
|
137
|
+
|
|
138
|
+
def get_public_tle(self, query: PublicSatelliteQuery) -> PublicTLE:
|
|
139
|
+
return self._model(PublicTLE, self._request("POST", "/public/tle", payload=query))
|
|
140
|
+
|
|
141
|
+
def get_public_state(self, query: PublicSatelliteQuery, timestamp: datetime | str) -> OrbitalStateResponse:
|
|
142
|
+
return self._model(OrbitalStateResponse, self._request("POST", "/public/state", params={"timestamp": timestamp}, payload=query))
|
|
143
|
+
|
|
144
|
+
def get_public_contacts(self, query: PublicContactQuery) -> list[ContactInterval]:
|
|
145
|
+
return self._models(ContactInterval, self._request("POST", "/public/contacts", payload=query))
|
|
146
|
+
|
|
147
|
+
def create_project(self, payload: ProjectCreate) -> Project:
|
|
148
|
+
return self._model(Project, self._request("POST", "/projects", payload=payload))
|
|
149
|
+
|
|
150
|
+
def list_projects(self) -> list[Project]:
|
|
151
|
+
return self._models(Project, self._request("GET", "/projects"))
|
|
152
|
+
|
|
153
|
+
def delete_project(self, project_id: str) -> Project:
|
|
154
|
+
return self._model(Project, self._request("DELETE", f"/projects/{project_id}"))
|
|
155
|
+
|
|
156
|
+
def create_satellite(self, project_id: str, payload: SatelliteCreate) -> Satellite:
|
|
157
|
+
return self._model(Satellite, self._request("POST", f"/projects/{project_id}/satellites", payload=payload))
|
|
158
|
+
|
|
159
|
+
def list_satellites(self, project_id: str) -> list[Satellite]:
|
|
160
|
+
return self._models(Satellite, self._request("GET", f"/projects/{project_id}/satellites"))
|
|
161
|
+
|
|
162
|
+
def delete_satellite(self, project_id: str, satellite_id: str) -> Satellite:
|
|
163
|
+
return self._model(Satellite, self._request("DELETE", f"/projects/{project_id}/satellites/{satellite_id}"))
|
|
164
|
+
|
|
165
|
+
def create_groundstation(self, project_id: str, payload: GroundStationCreate) -> GroundStation:
|
|
166
|
+
return self._model(GroundStation, self._request("POST", f"/projects/{project_id}/groundstations", payload=payload))
|
|
167
|
+
|
|
168
|
+
def list_groundstations(self, project_id: str) -> list[GroundStation]:
|
|
169
|
+
return self._models(GroundStation, self._request("GET", f"/projects/{project_id}/groundstations"))
|
|
170
|
+
|
|
171
|
+
def delete_groundstation(self, project_id: str, groundstation_id: str) -> GroundStation:
|
|
172
|
+
return self._model(GroundStation, self._request("DELETE", f"/projects/{project_id}/groundstations/{groundstation_id}"))
|
|
173
|
+
|
|
174
|
+
def get_satellite_state(self, project_id: str, satellite_id: str, timestamp: datetime | str) -> OrbitalStateResponse:
|
|
175
|
+
return self._model(OrbitalStateResponse, self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/state", params={"timestamp": timestamp}))
|
|
176
|
+
|
|
177
|
+
def get_satellite_states(self, project_id: str, satellite_id: str, payload: TimeRangeQuery) -> list[OrbitalStateResponse]:
|
|
178
|
+
return self._models(OrbitalStateResponse, self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/states", payload=payload))
|
|
179
|
+
|
|
180
|
+
def get_satellite_attitude(self, project_id: str, satellite_id: str, timestamp: datetime | str) -> AttitudeSample:
|
|
181
|
+
return self._model(AttitudeSample, self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/attitude", params={"timestamp": timestamp}))
|
|
182
|
+
|
|
183
|
+
def get_satellite_subsystems(self, project_id: str, satellite_id: str, timestamp: datetime | str) -> SatelliteSubsystemState:
|
|
184
|
+
return self._model(SatelliteSubsystemState, self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/subsystems", params={"timestamp": timestamp}))
|
|
185
|
+
|
|
186
|
+
def get_contacts(self, project_id: str, satellite_id: str, groundstation_id: str, payload: TimeRangeQuery) -> list[ContactInterval]:
|
|
187
|
+
return self._models(ContactInterval, self._request("POST", f"/projects/{project_id}/contacts/{satellite_id}/{groundstation_id}", payload=payload))
|
|
188
|
+
|
|
189
|
+
def get_link_budget(self, project_id: str, payload: LinkBudgetRequest) -> LinkBudgetResponse:
|
|
190
|
+
return self._model(LinkBudgetResponse, self._request("POST", f"/projects/{project_id}/links/budget", payload=payload))
|
|
191
|
+
|
|
192
|
+
def create_constellation(self, project_id: str, payload: ConstellationCreate) -> Constellation:
|
|
193
|
+
return self._model(Constellation, self._request("POST", f"/projects/{project_id}/constellations", payload=payload))
|
|
194
|
+
|
|
195
|
+
def design_constellation(self, project_id: str, payload: ConstellationDesignCreate) -> ConstellationDesignResponse:
|
|
196
|
+
return self._model(ConstellationDesignResponse, self._request("POST", f"/projects/{project_id}/constellations/design", payload=payload))
|
|
197
|
+
|
|
198
|
+
def list_constellations(self, project_id: str) -> list[Constellation]:
|
|
199
|
+
return self._models(Constellation, self._request("GET", f"/projects/{project_id}/constellations"))
|
|
200
|
+
|
|
201
|
+
def delete_constellation(self, project_id: str, constellation_id: str) -> Constellation:
|
|
202
|
+
return self._model(Constellation, self._request("DELETE", f"/projects/{project_id}/constellations/{constellation_id}"))
|
|
203
|
+
|
|
204
|
+
def get_constellation_network(self, project_id: str, constellation_id: str, timestamp: datetime | str, kind: str = "rf") -> NetworkResponse:
|
|
205
|
+
return self._model(NetworkResponse, self._request("POST", f"/projects/{project_id}/constellations/{constellation_id}/network", params={"timestamp": timestamp, "kind": kind}))
|
|
206
|
+
|
|
207
|
+
def admin_dashboard(self) -> AdminDashboard:
|
|
208
|
+
return self._model(AdminDashboard, self._request("GET", "/admin/dashboard"))
|
|
209
|
+
|
|
210
|
+
def admin_list_users(self) -> list[UserProfile]:
|
|
211
|
+
return self._models(UserProfile, self._request("GET", "/admin/users"))
|
|
212
|
+
|
|
213
|
+
def admin_list_projects(self) -> list[AdminProjectInfo]:
|
|
214
|
+
return self._models(AdminProjectInfo, self._request("GET", "/admin/projects"))
|
|
215
|
+
|
|
216
|
+
def admin_user_tokens(self, user_id: str) -> list[TokenInfo]:
|
|
217
|
+
return self._models(TokenInfo, self._request("GET", f"/admin/users/{user_id}/tokens"))
|
|
218
|
+
|
|
219
|
+
def admin_toggle_admin(self, user_id: str) -> UserProfile:
|
|
220
|
+
return self._model(UserProfile, self._request("POST", f"/admin/users/{user_id}/toggle-admin"))
|
|
221
|
+
|
|
222
|
+
def admin_revoke_token(self, token_id: str) -> TokenInfo:
|
|
223
|
+
return self._model(TokenInfo, self._request("POST", f"/admin/tokens/{token_id}/revoke"))
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class AsyncOrbsimClient(_OrbsimClientBase):
|
|
227
|
+
def __init__(
|
|
228
|
+
self,
|
|
229
|
+
base_url: str,
|
|
230
|
+
token: str | None = None,
|
|
231
|
+
timeout: float = 30.0,
|
|
232
|
+
client: httpx.AsyncClient | None = None,
|
|
233
|
+
) -> None:
|
|
234
|
+
self.base_url = base_url.rstrip("/")
|
|
235
|
+
self._client = client or httpx.AsyncClient(base_url=self.base_url, timeout=timeout)
|
|
236
|
+
self._owns_client = client is None
|
|
237
|
+
if token:
|
|
238
|
+
self.set_token(token)
|
|
239
|
+
|
|
240
|
+
async def aclose(self) -> None:
|
|
241
|
+
if self._owns_client:
|
|
242
|
+
await self._client.aclose()
|
|
243
|
+
|
|
244
|
+
async def __aenter__(self) -> "AsyncOrbsimClient":
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
248
|
+
await self.aclose()
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def token(self) -> str | None:
|
|
252
|
+
auth = self._client.headers.get("Authorization", "")
|
|
253
|
+
if auth.startswith("Bearer "):
|
|
254
|
+
return auth.removeprefix("Bearer ")
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def set_token(self, token: str | None) -> None:
|
|
258
|
+
if token:
|
|
259
|
+
self._client.headers["Authorization"] = f"Bearer {token}"
|
|
260
|
+
else:
|
|
261
|
+
self._client.headers.pop("Authorization", None)
|
|
262
|
+
|
|
263
|
+
async def _request(self, method: str, path: str, *, params: dict[str, Any] | None = None, payload: BaseModel | dict[str, Any] | None = None) -> Any:
|
|
264
|
+
json_payload: dict[str, Any] | None = None
|
|
265
|
+
if payload is not None:
|
|
266
|
+
json_payload = payload.model_dump(mode="json", exclude_none=True) if isinstance(payload, BaseModel) else payload
|
|
267
|
+
response = await self._client.request(method, path, params=params, json=json_payload)
|
|
268
|
+
if response.is_error:
|
|
269
|
+
try:
|
|
270
|
+
detail = response.json().get("detail", response.text)
|
|
271
|
+
except Exception:
|
|
272
|
+
detail = response.text
|
|
273
|
+
raise OrbsimAPIError(response.status_code, detail)
|
|
274
|
+
if not response.content:
|
|
275
|
+
return None
|
|
276
|
+
return response.json()
|
|
277
|
+
|
|
278
|
+
async def bootstrap_admin(self, email: str, password: str, full_name: str) -> AuthTokenResponse:
|
|
279
|
+
result = self._model(AuthTokenResponse, await self._request("POST", "/auth/bootstrap-admin", payload=BootstrapAdminRequest(email=email, password=password, full_name=full_name)))
|
|
280
|
+
self.set_token(result.token)
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
async def register(self, email: str, password: str, full_name: str) -> AuthTokenResponse:
|
|
284
|
+
result = self._model(AuthTokenResponse, await self._request("POST", "/auth/register", payload=RegisterRequest(email=email, password=password, full_name=full_name)))
|
|
285
|
+
self.set_token(result.token)
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
async def login(self, email: str, password: str, token_name: str = "Default API Token") -> AuthTokenResponse:
|
|
289
|
+
result = self._model(AuthTokenResponse, await self._request("POST", "/auth/login", payload=LoginRequest(email=email, password=password, token_name=token_name)))
|
|
290
|
+
self.set_token(result.token)
|
|
291
|
+
return result
|
|
292
|
+
|
|
293
|
+
async def me(self) -> UserProfile:
|
|
294
|
+
return self._model(UserProfile, await self._request("GET", "/auth/me"))
|
|
295
|
+
|
|
296
|
+
async def list_tokens(self) -> list[TokenInfo]:
|
|
297
|
+
return self._models(TokenInfo, await self._request("GET", "/auth/tokens"))
|
|
298
|
+
|
|
299
|
+
async def create_token(self, name: str) -> TokenCreateResponse:
|
|
300
|
+
return self._model(TokenCreateResponse, await self._request("POST", "/auth/tokens", payload=TokenCreateRequest(name=name)))
|
|
301
|
+
|
|
302
|
+
async def revoke_own_token(self, token_id: str) -> TokenInfo:
|
|
303
|
+
return self._model(TokenInfo, await self._request("DELETE", f"/auth/tokens/{token_id}"))
|
|
304
|
+
|
|
305
|
+
async def health(self) -> HealthResponse:
|
|
306
|
+
return self._model(HealthResponse, await self._request("GET", "/health"))
|
|
307
|
+
|
|
308
|
+
async def get_public_tle(self, query: PublicSatelliteQuery) -> PublicTLE:
|
|
309
|
+
return self._model(PublicTLE, await self._request("POST", "/public/tle", payload=query))
|
|
310
|
+
|
|
311
|
+
async def get_public_state(self, query: PublicSatelliteQuery, timestamp: datetime | str) -> OrbitalStateResponse:
|
|
312
|
+
return self._model(OrbitalStateResponse, await self._request("POST", "/public/state", params={"timestamp": timestamp}, payload=query))
|
|
313
|
+
|
|
314
|
+
async def get_public_contacts(self, query: PublicContactQuery) -> list[ContactInterval]:
|
|
315
|
+
return self._models(ContactInterval, await self._request("POST", "/public/contacts", payload=query))
|
|
316
|
+
|
|
317
|
+
async def create_project(self, payload: ProjectCreate) -> Project:
|
|
318
|
+
return self._model(Project, await self._request("POST", "/projects", payload=payload))
|
|
319
|
+
|
|
320
|
+
async def list_projects(self) -> list[Project]:
|
|
321
|
+
return self._models(Project, await self._request("GET", "/projects"))
|
|
322
|
+
|
|
323
|
+
async def delete_project(self, project_id: str) -> Project:
|
|
324
|
+
return self._model(Project, await self._request("DELETE", f"/projects/{project_id}"))
|
|
325
|
+
|
|
326
|
+
async def create_satellite(self, project_id: str, payload: SatelliteCreate) -> Satellite:
|
|
327
|
+
return self._model(Satellite, await self._request("POST", f"/projects/{project_id}/satellites", payload=payload))
|
|
328
|
+
|
|
329
|
+
async def list_satellites(self, project_id: str) -> list[Satellite]:
|
|
330
|
+
return self._models(Satellite, await self._request("GET", f"/projects/{project_id}/satellites"))
|
|
331
|
+
|
|
332
|
+
async def delete_satellite(self, project_id: str, satellite_id: str) -> Satellite:
|
|
333
|
+
return self._model(Satellite, await self._request("DELETE", f"/projects/{project_id}/satellites/{satellite_id}"))
|
|
334
|
+
|
|
335
|
+
async def create_groundstation(self, project_id: str, payload: GroundStationCreate) -> GroundStation:
|
|
336
|
+
return self._model(GroundStation, await self._request("POST", f"/projects/{project_id}/groundstations", payload=payload))
|
|
337
|
+
|
|
338
|
+
async def list_groundstations(self, project_id: str) -> list[GroundStation]:
|
|
339
|
+
return self._models(GroundStation, await self._request("GET", f"/projects/{project_id}/groundstations"))
|
|
340
|
+
|
|
341
|
+
async def delete_groundstation(self, project_id: str, groundstation_id: str) -> GroundStation:
|
|
342
|
+
return self._model(GroundStation, await self._request("DELETE", f"/projects/{project_id}/groundstations/{groundstation_id}"))
|
|
343
|
+
|
|
344
|
+
async def get_satellite_state(self, project_id: str, satellite_id: str, timestamp: datetime | str) -> OrbitalStateResponse:
|
|
345
|
+
return self._model(OrbitalStateResponse, await self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/state", params={"timestamp": timestamp}))
|
|
346
|
+
|
|
347
|
+
async def get_satellite_states(self, project_id: str, satellite_id: str, payload: TimeRangeQuery) -> list[OrbitalStateResponse]:
|
|
348
|
+
return self._models(OrbitalStateResponse, await self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/states", payload=payload))
|
|
349
|
+
|
|
350
|
+
async def get_satellite_attitude(self, project_id: str, satellite_id: str, timestamp: datetime | str) -> AttitudeSample:
|
|
351
|
+
return self._model(AttitudeSample, await self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/attitude", params={"timestamp": timestamp}))
|
|
352
|
+
|
|
353
|
+
async def get_satellite_subsystems(self, project_id: str, satellite_id: str, timestamp: datetime | str) -> SatelliteSubsystemState:
|
|
354
|
+
return self._model(SatelliteSubsystemState, await self._request("POST", f"/projects/{project_id}/satellites/{satellite_id}/subsystems", params={"timestamp": timestamp}))
|
|
355
|
+
|
|
356
|
+
async def get_contacts(self, project_id: str, satellite_id: str, groundstation_id: str, payload: TimeRangeQuery) -> list[ContactInterval]:
|
|
357
|
+
return self._models(ContactInterval, await self._request("POST", f"/projects/{project_id}/contacts/{satellite_id}/{groundstation_id}", payload=payload))
|
|
358
|
+
|
|
359
|
+
async def get_link_budget(self, project_id: str, payload: LinkBudgetRequest) -> LinkBudgetResponse:
|
|
360
|
+
return self._model(LinkBudgetResponse, await self._request("POST", f"/projects/{project_id}/links/budget", payload=payload))
|
|
361
|
+
|
|
362
|
+
async def create_constellation(self, project_id: str, payload: ConstellationCreate) -> Constellation:
|
|
363
|
+
return self._model(Constellation, await self._request("POST", f"/projects/{project_id}/constellations", payload=payload))
|
|
364
|
+
|
|
365
|
+
async def design_constellation(self, project_id: str, payload: ConstellationDesignCreate) -> ConstellationDesignResponse:
|
|
366
|
+
return self._model(ConstellationDesignResponse, await self._request("POST", f"/projects/{project_id}/constellations/design", payload=payload))
|
|
367
|
+
|
|
368
|
+
async def list_constellations(self, project_id: str) -> list[Constellation]:
|
|
369
|
+
return self._models(Constellation, await self._request("GET", f"/projects/{project_id}/constellations"))
|
|
370
|
+
|
|
371
|
+
async def delete_constellation(self, project_id: str, constellation_id: str) -> Constellation:
|
|
372
|
+
return self._model(Constellation, await self._request("DELETE", f"/projects/{project_id}/constellations/{constellation_id}"))
|
|
373
|
+
|
|
374
|
+
async def get_constellation_network(self, project_id: str, constellation_id: str, timestamp: datetime | str, kind: str = "rf") -> NetworkResponse:
|
|
375
|
+
return self._model(NetworkResponse, await self._request("POST", f"/projects/{project_id}/constellations/{constellation_id}/network", params={"timestamp": timestamp, "kind": kind}))
|
|
376
|
+
|
|
377
|
+
async def admin_dashboard(self) -> AdminDashboard:
|
|
378
|
+
return self._model(AdminDashboard, await self._request("GET", "/admin/dashboard"))
|
|
379
|
+
|
|
380
|
+
async def admin_list_users(self) -> list[UserProfile]:
|
|
381
|
+
return self._models(UserProfile, await self._request("GET", "/admin/users"))
|
|
382
|
+
|
|
383
|
+
async def admin_list_projects(self) -> list[AdminProjectInfo]:
|
|
384
|
+
return self._models(AdminProjectInfo, await self._request("GET", "/admin/projects"))
|
|
385
|
+
|
|
386
|
+
async def admin_user_tokens(self, user_id: str) -> list[TokenInfo]:
|
|
387
|
+
return self._models(TokenInfo, await self._request("GET", f"/admin/users/{user_id}/tokens"))
|
|
388
|
+
|
|
389
|
+
async def admin_toggle_admin(self, user_id: str) -> UserProfile:
|
|
390
|
+
return self._model(UserProfile, await self._request("POST", f"/admin/users/{user_id}/toggle-admin"))
|
|
391
|
+
|
|
392
|
+
async def admin_revoke_token(self, token_id: str) -> TokenInfo:
|
|
393
|
+
return self._model(TokenInfo, await self._request("POST", f"/admin/tokens/{token_id}/revoke"))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class OrbsimSDKError(Exception):
|
|
2
|
+
"""Base SDK exception."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OrbsimAPIError(OrbsimSDKError):
|
|
6
|
+
def __init__(self, status_code: int, detail: str):
|
|
7
|
+
self.status_code = status_code
|
|
8
|
+
self.detail = detail
|
|
9
|
+
super().__init__(f"OrbSim API error {status_code}: {detail}")
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, HttpUrl
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrbitSource(str, Enum):
|
|
11
|
+
TLE = "tle"
|
|
12
|
+
EPHEMERIS = "ephemeris"
|
|
13
|
+
KEPLERIAN = "keplerian"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AnomalyKind(str, Enum):
|
|
17
|
+
TRUE = "true"
|
|
18
|
+
MEAN = "mean"
|
|
19
|
+
ECCENTRIC = "eccentric"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AttitudeMode(str, Enum):
|
|
23
|
+
LVLH = "lvlh"
|
|
24
|
+
NADIR = "nadir"
|
|
25
|
+
SUN_POINTING = "sun_pointing"
|
|
26
|
+
INERTIAL = "inertial"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LinkKind(str, Enum):
|
|
30
|
+
RF = "rf"
|
|
31
|
+
OPTICAL = "optical"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ConstellationDesignType(str, Enum):
|
|
35
|
+
WALKER_DELTA = "walker_delta"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LoginRequest(BaseModel):
|
|
39
|
+
email: str
|
|
40
|
+
password: str
|
|
41
|
+
token_name: str = "Default API Token"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RegisterRequest(BaseModel):
|
|
45
|
+
email: str
|
|
46
|
+
password: str
|
|
47
|
+
full_name: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BootstrapAdminRequest(RegisterRequest):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class UserProfile(BaseModel):
|
|
55
|
+
id: str
|
|
56
|
+
email: str
|
|
57
|
+
full_name: str
|
|
58
|
+
is_admin: bool
|
|
59
|
+
is_active: bool
|
|
60
|
+
created_at: datetime
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AuthTokenResponse(BaseModel):
|
|
64
|
+
token: str
|
|
65
|
+
token_type: Literal["bearer"] = "bearer"
|
|
66
|
+
user: UserProfile
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TokenCreateRequest(BaseModel):
|
|
70
|
+
name: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TokenInfo(BaseModel):
|
|
74
|
+
id: str
|
|
75
|
+
name: str
|
|
76
|
+
token_prefix: str
|
|
77
|
+
created_at: datetime
|
|
78
|
+
last_used_at: datetime | None = None
|
|
79
|
+
revoked_at: datetime | None = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TokenCreateResponse(BaseModel):
|
|
83
|
+
token: str
|
|
84
|
+
token_info: TokenInfo
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ProjectCreate(BaseModel):
|
|
88
|
+
name: str
|
|
89
|
+
description: str | None = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Project(BaseModel):
|
|
93
|
+
id: str
|
|
94
|
+
name: str
|
|
95
|
+
description: str | None = None
|
|
96
|
+
created_at: datetime
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class CartesianState(BaseModel):
|
|
100
|
+
timestamp: datetime
|
|
101
|
+
position_m: list[float] = Field(min_length=3, max_length=3)
|
|
102
|
+
velocity_mps: list[float] = Field(min_length=3, max_length=3)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TLEDefinition(BaseModel):
|
|
106
|
+
line1: str
|
|
107
|
+
line2: str
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class KeplerianDefinition(BaseModel):
|
|
111
|
+
semi_major_axis_m: float
|
|
112
|
+
eccentricity: float
|
|
113
|
+
inclination_deg: float
|
|
114
|
+
raan_deg: float
|
|
115
|
+
arg_perigee_deg: float
|
|
116
|
+
anomaly_deg: float
|
|
117
|
+
anomaly_kind: AnomalyKind = AnomalyKind.TRUE
|
|
118
|
+
epoch: datetime
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class SatelliteSubsystemState(BaseModel):
|
|
122
|
+
power_w: float = 0.0
|
|
123
|
+
battery_soc: float = 1.0
|
|
124
|
+
thermal_c: float = 20.0
|
|
125
|
+
payload_active: bool = False
|
|
126
|
+
comms_margin_db: float = 0.0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class SatelliteCreate(BaseModel):
|
|
130
|
+
name: str
|
|
131
|
+
source: OrbitSource
|
|
132
|
+
tle: TLEDefinition | None = None
|
|
133
|
+
ephemeris: list[CartesianState] | None = None
|
|
134
|
+
keplerian: KeplerianDefinition | None = None
|
|
135
|
+
attitude_mode: AttitudeMode = AttitudeMode.NADIR
|
|
136
|
+
dry_mass_kg: float = 100.0
|
|
137
|
+
subsystems: SatelliteSubsystemState = Field(default_factory=SatelliteSubsystemState)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Satellite(BaseModel):
|
|
141
|
+
id: str
|
|
142
|
+
name: str
|
|
143
|
+
source: OrbitSource
|
|
144
|
+
tle: TLEDefinition | None = None
|
|
145
|
+
ephemeris: list[CartesianState] | None = None
|
|
146
|
+
keplerian: KeplerianDefinition | None = None
|
|
147
|
+
attitude_mode: AttitudeMode
|
|
148
|
+
dry_mass_kg: float
|
|
149
|
+
subsystems: SatelliteSubsystemState
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class GroundStationCreate(BaseModel):
|
|
153
|
+
name: str
|
|
154
|
+
latitude_deg: float
|
|
155
|
+
longitude_deg: float
|
|
156
|
+
altitude_m: float = 0.0
|
|
157
|
+
min_elevation_deg: float = 5.0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class GroundStation(BaseModel):
|
|
161
|
+
id: str
|
|
162
|
+
name: str
|
|
163
|
+
latitude_deg: float
|
|
164
|
+
longitude_deg: float
|
|
165
|
+
altitude_m: float = 0.0
|
|
166
|
+
min_elevation_deg: float = 5.0
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class TimeRangeQuery(BaseModel):
|
|
170
|
+
start: datetime
|
|
171
|
+
end: datetime
|
|
172
|
+
step_seconds: int = 60
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class AttitudeSample(BaseModel):
|
|
176
|
+
timestamp: datetime
|
|
177
|
+
quaternion_xyzw: list[float]
|
|
178
|
+
euler_deg: list[float]
|
|
179
|
+
mode: AttitudeMode
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class OrbitalStateResponse(BaseModel):
|
|
183
|
+
satellite_id: str
|
|
184
|
+
timestamp: datetime
|
|
185
|
+
position_m: list[float]
|
|
186
|
+
velocity_mps: list[float]
|
|
187
|
+
inertial_position_m: list[float] | None = None
|
|
188
|
+
inertial_velocity_mps: list[float] | None = None
|
|
189
|
+
altitude_m: float
|
|
190
|
+
latitude_deg: float
|
|
191
|
+
longitude_deg: float
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ContactInterval(BaseModel):
|
|
195
|
+
start: datetime
|
|
196
|
+
end: datetime
|
|
197
|
+
max_elevation_deg: float
|
|
198
|
+
satellite_id: str
|
|
199
|
+
groundstation_id: str
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class LinkBudgetRequest(BaseModel):
|
|
203
|
+
source_satellite_id: str
|
|
204
|
+
target_satellite_id: str | None = None
|
|
205
|
+
groundstation_id: str | None = None
|
|
206
|
+
kind: LinkKind
|
|
207
|
+
timestamp: datetime
|
|
208
|
+
frequency_hz: float = 8.2e9
|
|
209
|
+
tx_power_dbw: float = 10.0
|
|
210
|
+
tx_gain_dbi: float = 25.0
|
|
211
|
+
rx_gain_dbi: float = 25.0
|
|
212
|
+
bandwidth_hz: float = 1e6
|
|
213
|
+
system_temperature_k: float = 500.0
|
|
214
|
+
optical_wavelength_nm: float = 1550.0
|
|
215
|
+
aperture_m: float = 0.12
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class LinkBudgetResponse(BaseModel):
|
|
219
|
+
kind: LinkKind
|
|
220
|
+
range_m: float
|
|
221
|
+
free_space_loss_db: float
|
|
222
|
+
rx_power_dbw: float
|
|
223
|
+
snr_db: float
|
|
224
|
+
visible: bool
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ConstellationCreate(BaseModel):
|
|
228
|
+
name: str
|
|
229
|
+
satellite_ids: list[str]
|
|
230
|
+
isl_max_range_m: float = 3_500_000.0
|
|
231
|
+
max_degree: int = 4
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class WalkerDeltaDefinition(BaseModel):
|
|
235
|
+
total_satellites: int
|
|
236
|
+
plane_count: int
|
|
237
|
+
relative_spacing: int
|
|
238
|
+
semi_major_axis_m: float
|
|
239
|
+
eccentricity: float
|
|
240
|
+
inclination_deg: float
|
|
241
|
+
arg_perigee_deg: float = 0.0
|
|
242
|
+
start_raan_deg: float = 0.0
|
|
243
|
+
start_anomaly_deg: float = 0.0
|
|
244
|
+
anomaly_kind: AnomalyKind = AnomalyKind.MEAN
|
|
245
|
+
satellite_name_prefix: str | None = None
|
|
246
|
+
attitude_mode: AttitudeMode = AttitudeMode.NADIR
|
|
247
|
+
dry_mass_kg: float = 100.0
|
|
248
|
+
subsystems: SatelliteSubsystemState = Field(default_factory=SatelliteSubsystemState)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ConstellationDesignCreate(BaseModel):
|
|
252
|
+
name: str
|
|
253
|
+
type: ConstellationDesignType
|
|
254
|
+
walker_delta: WalkerDeltaDefinition | None = None
|
|
255
|
+
isl_max_range_m: float = 3_500_000.0
|
|
256
|
+
max_degree: int = 4
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Constellation(BaseModel):
|
|
260
|
+
id: str
|
|
261
|
+
name: str
|
|
262
|
+
satellite_ids: list[str]
|
|
263
|
+
isl_max_range_m: float
|
|
264
|
+
max_degree: int
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class ConstellationDesignResponse(BaseModel):
|
|
268
|
+
constellation: Constellation
|
|
269
|
+
satellites: list[Satellite]
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class PublicSatelliteQuery(BaseModel):
|
|
273
|
+
norad_id: str | None = None
|
|
274
|
+
name: str | None = None
|
|
275
|
+
source_url: HttpUrl | None = None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class PublicTLE(BaseModel):
|
|
279
|
+
name: str
|
|
280
|
+
line1: str
|
|
281
|
+
line2: str
|
|
282
|
+
source: str
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class PublicContactQuery(BaseModel):
|
|
286
|
+
satellite: PublicSatelliteQuery
|
|
287
|
+
groundstation: GroundStationCreate
|
|
288
|
+
start: datetime
|
|
289
|
+
end: datetime
|
|
290
|
+
step_seconds: int = 60
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class AdminDashboard(BaseModel):
|
|
294
|
+
user_count: int
|
|
295
|
+
project_count: int
|
|
296
|
+
token_count: int
|
|
297
|
+
active_token_count: int
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class HealthResponse(BaseModel):
|
|
301
|
+
status: Literal["ok"]
|
|
302
|
+
orekit_available: bool
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class AdminProjectInfo(BaseModel):
|
|
306
|
+
id: str
|
|
307
|
+
name: str
|
|
308
|
+
description: str | None = None
|
|
309
|
+
created_at: datetime
|
|
310
|
+
owner_email: str
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class NetworkResponse(BaseModel):
|
|
314
|
+
timestamp: datetime
|
|
315
|
+
kind: str
|
|
316
|
+
nodes: dict[str, OrbitalStateResponse]
|
|
317
|
+
adjacency: dict[str, list[dict[str, Any]]]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orbsim-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the OrbSim API
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: pydantic>=2.8.0
|
|
9
|
+
|
|
10
|
+
# OrbSim SDK
|
|
11
|
+
|
|
12
|
+
Typed Python client for the OrbSim API.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install /path/to/orbsim/sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from orbsim_sdk import OrbitSource, OrbsimClient, SatelliteCreate, TLEDefinition
|
|
24
|
+
|
|
25
|
+
client = OrbsimClient("http://127.0.0.1:8000/api")
|
|
26
|
+
auth = client.login("admin@example.com", "supersecret123", token_name="SDK Session")
|
|
27
|
+
projects = client.list_projects()
|
|
28
|
+
|
|
29
|
+
satellite = client.create_satellite(
|
|
30
|
+
projects[0].id,
|
|
31
|
+
SatelliteCreate(
|
|
32
|
+
name="ISS Copy",
|
|
33
|
+
source=OrbitSource.TLE,
|
|
34
|
+
tle=TLEDefinition(
|
|
35
|
+
line1="1 25544U 98067A 26099.50000000 .00016717 00000+0 30747-3 0 9991",
|
|
36
|
+
line2="2 25544 51.6400 54.1200 0003870 123.4567 321.6543 15.50000000123456",
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
state = client.get_satellite_state(projects[0].id, satellite.id, "2026-04-09T12:00:00Z")
|
|
42
|
+
print(state.latitude_deg, state.longitude_deg, state.altitude_m)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Async Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
|
|
50
|
+
from orbsim_sdk import AsyncOrbsimClient
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def main():
|
|
54
|
+
async with AsyncOrbsimClient("http://127.0.0.1:8000/api") as client:
|
|
55
|
+
await client.login("admin@example.com", "supersecret123", token_name="Async SDK Session")
|
|
56
|
+
projects = await client.list_projects()
|
|
57
|
+
print([project.name for project in projects])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/orbsim_sdk/__init__.py
|
|
4
|
+
src/orbsim_sdk/client.py
|
|
5
|
+
src/orbsim_sdk/exceptions.py
|
|
6
|
+
src/orbsim_sdk/models.py
|
|
7
|
+
src/orbsim_sdk.egg-info/PKG-INFO
|
|
8
|
+
src/orbsim_sdk.egg-info/SOURCES.txt
|
|
9
|
+
src/orbsim_sdk.egg-info/dependency_links.txt
|
|
10
|
+
src/orbsim_sdk.egg-info/requires.txt
|
|
11
|
+
src/orbsim_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
orbsim_sdk
|