Skip to content

Commit ed43d50

Browse files
Merge pull request #1 from mudhoney/feature/multiple-hgs-conversion
implement post hgs2hpc conversion api endpoints and tests for it
2 parents fcea831 + 9a25778 commit ed43d50

File tree

4 files changed

+187
-25
lines changed

4 files changed

+187
-25
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build and Push to Docker Hub
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
tags:
7+
- v**
8+
9+
jobs:
10+
# Build and push containers to Docker Hub
11+
docker:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
- name: Set up QEMU
18+
uses: docker/setup-qemu-action@v3
19+
- name: Set up Docker Buildx
20+
uses: docker/setup-buildx-action@v3
21+
- name: Login to Docker Hub
22+
uses: docker/login-action@v3
23+
with:
24+
username: ${{ secrets.DOCKERHUB_USERNAME }}
25+
password: ${{ secrets.DOCKERHUB_TOKEN }}
26+
- name: Docker meta
27+
id: meta
28+
uses: docker/metadata-action@v5
29+
with:
30+
images: dgarciabriseno/hv-coordinator
31+
# generate Docker tags based on the tagged release
32+
tags: type=semver,pattern={{version}}
33+
- name: Build and push Docker images
34+
uses: docker/build-push-action@v5.3.0
35+
with:
36+
# Build's context is the set of files located in the specified PATH or URL
37+
context: app
38+
# List of target platforms for build
39+
platforms: linux/amd64,linux/arm64
40+
# List of tags
41+
tags: ${{ steps.meta.outputs.tags }}
42+
labels: ${{ steps.meta.outputs.labels }}
43+
# Only push containers on version tags
44+
push: ${{ github.event_name == 'push' && github.ref_type == 'tag'}}

.github/workflows/workflow.yml

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
name: Test and Deploy
1+
name: Test and Build
22

33
on:
4-
push:
5-
branches: [ "main" ]
6-
tags:
7-
- v**
84
pull_request:
95
branches: [ "main" ]
106

@@ -60,7 +56,7 @@ jobs:
6056
retention-days: 30
6157

6258

63-
# Build and push containers to docker
59+
# Build Docker images to verify they can be built
6460
docker:
6561
runs-on: ubuntu-latest
6662

@@ -71,28 +67,14 @@ jobs:
7167
uses: docker/setup-qemu-action@v3
7268
- name: Set up Docker Buildx
7369
uses: docker/setup-buildx-action@v3
74-
- name: Login to Docker Hub
75-
uses: docker/login-action@v3
76-
with:
77-
username: ${{ secrets.DOCKERHUB_USERNAME }}
78-
password: ${{ secrets.DOCKERHUB_TOKEN }}
79-
- name: Docker meta
80-
id: meta
81-
uses: docker/metadata-action@v5
82-
with:
83-
images: dgarciabriseno/hv-coordinator
84-
# generate Docker tags based on the tagged release
85-
tags: type=semver,pattern={{version}}
86-
- name: Build and push Docker images
70+
- name: Build Docker images
8771
uses: docker/build-push-action@v5.3.0
8872
with:
8973
# Build's context is the set of files located in the specified PATH or URL
9074
context: app
9175
# List of target platforms for build
9276
platforms: linux/amd64,linux/arm64
93-
# List of tags
94-
tags: ${{ steps.meta.outputs.tags }}
95-
labels: ${{ steps.meta.outputs.labels }}
96-
# Only push containers on version tags
97-
push: ${{ github.event_name == 'push' && github.ref_type == 'tag'}}
77+
# Don't push, just build to verify
78+
push: false
79+
tags: hv-coordinator:buildtest
9880

app/main.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,31 @@ def _hgs2hpc(params: Annotated[Hgs2HpcQueryParameters, Query()]):
4747
return {"x": coord.Tx.value, "y": coord.Ty.value}
4848

4949

50+
class Hgs2HpcCoordInput(HvBaseModel):
51+
lat: float = Field(ge=-90, le=90)
52+
lon: float
53+
coord_time: AstropyTime
54+
55+
56+
class Hgs2HpcBatchInput(HvBaseModel):
57+
coordinates: List[Hgs2HpcCoordInput]
58+
target: AstropyTime
59+
60+
61+
@app.post(
62+
"/hgs2hpc",
63+
summary="Convert Heliographic Stonyhurst coordinate to Helioprojective coordinate in Helioviewer's POV",
64+
)
65+
def _hgs2hpc_post(params: Hgs2HpcBatchInput):
66+
"Convert a latitude/longitude coordinate to the equivalent helioprojective coordinate at the given target time"
67+
coords = map(
68+
lambda c: hgs2hpc(c.lat, c.lon, c.coord_time, params.target), params.coordinates
69+
)
70+
return {
71+
"coordinates": [{"x": coord.Tx.value, "y": coord.Ty.value} for coord in coords]
72+
}
73+
74+
5075
class NormalizeHpcQueryParameters(HvBaseModel):
5176
x: float
5277
y: float

app/test/test_api.py

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,117 @@ def test_hgs2hpc(client: TestClient):
9494
assert data["detail"][0]["loc"][1] == "lat"
9595

9696

97+
def test_hgs2hpc_post(client: TestClient):
98+
"""
99+
Test POST /hgs2hpc endpoint with batch coordinate conversion
100+
"""
101+
# Test valid batch request
102+
batch_data = {
103+
"coordinates": [
104+
{"lat": 0.0, "lon": 0.0, "coord_time": "2012-01-01T00:00:00Z"},
105+
{"lat": 10.0, "lon": 20.0, "coord_time": "2012-01-01T00:00:00Z"},
106+
],
107+
"target": "2012-01-01T00:00:00Z",
108+
}
109+
response = client.post("/hgs2hpc", json=batch_data)
110+
assert response.status_code == 200
111+
data = response.json()
112+
assert "coordinates" in data
113+
assert len(data["coordinates"]) == 2
114+
assert pytest.approx(data["coordinates"][0]["x"], abs=0.1) == 0
115+
assert pytest.approx(data["coordinates"][0]["y"], abs=50) == 49.558
116+
assert pytest.approx(data["coordinates"][1]["x"], abs=0.1) == 324.471
117+
assert pytest.approx(data["coordinates"][1]["y"], abs=50) == 212.902
118+
119+
# Test with differential rotation (target time different from coord_time)
120+
batch_data = {
121+
"coordinates": [
122+
{"lat": 0.0, "lon": 0.0, "coord_time": "2012-01-01T00:00:00Z"},
123+
],
124+
"target": "2012-01-01T01:00:00Z",
125+
}
126+
response = client.post("/hgs2hpc", json=batch_data)
127+
assert response.status_code == 200
128+
data = response.json()
129+
assert len(data["coordinates"]) == 1
130+
# The x coordinate should move approximately 9 arcseconds
131+
assert 9 < data["coordinates"][0]["x"] and data["coordinates"][0]["x"] < 10
132+
133+
# Test missing coordinates field
134+
response = client.post("/hgs2hpc", json={"target": "2012-01-01T00:00:00Z"})
135+
assert response.status_code == 422
136+
137+
# Test missing target field
138+
response = client.post(
139+
"/hgs2hpc",
140+
json={
141+
"coordinates": [
142+
{"lat": 0.0, "lon": 0.0, "coord_time": "2012-01-01T00:00:00Z"}
143+
]
144+
},
145+
)
146+
assert response.status_code == 422
147+
148+
# Test invalid latitude (> 90)
149+
batch_data = {
150+
"coordinates": [
151+
{"lat": 90.1, "lon": 0.0, "coord_time": "2012-01-01T00:00:00Z"}
152+
],
153+
"target": "2012-01-01T00:00:00Z",
154+
}
155+
response = client.post("/hgs2hpc", json=batch_data)
156+
assert response.status_code == 422
157+
data = response.json()
158+
assert data["detail"][0]["loc"][-1] == "lat"
159+
160+
# Test invalid latitude (< -90)
161+
batch_data = {
162+
"coordinates": [
163+
{"lat": -90.1, "lon": 0.0, "coord_time": "2012-01-01T00:00:00Z"}
164+
],
165+
"target": "2012-01-01T00:00:00Z",
166+
}
167+
response = client.post("/hgs2hpc", json=batch_data)
168+
assert response.status_code == 422
169+
data = response.json()
170+
assert data["detail"][0]["loc"][-1] == "lat"
171+
172+
# Test invalid time format
173+
batch_data = {
174+
"coordinates": [{"lat": 0.0, "lon": 0.0, "coord_time": "NotAValidTime"}],
175+
"target": "2012-01-01T00:00:00Z",
176+
}
177+
response = client.post("/hgs2hpc", json=batch_data)
178+
assert response.status_code == 422
179+
180+
# Test empty coordinates array
181+
batch_data = {"coordinates": [], "target": "2012-01-01T00:00:00Z"}
182+
response = client.post("/hgs2hpc", json=batch_data)
183+
assert response.status_code == 200
184+
data = response.json()
185+
assert len(data["coordinates"]) == 0
186+
187+
# Test multiple coordinates with different coord_times
188+
batch_data = {
189+
"coordinates": [
190+
{"lat": 11.0, "lon": 21.0, "coord_time": "2024-01-01T00:00:00"},
191+
{"lat": 15.0, "lon": 26.0, "coord_time": "2025-01-01T00:00:00"},
192+
{"lat": 11.0, "lon": 21.0, "coord_time": "2022-01-01T00:00:00"},
193+
],
194+
"target": "2024-01-02T00:00:00",
195+
}
196+
response = client.post("/hgs2hpc", json=batch_data)
197+
assert response.status_code == 200
198+
data = response.json()
199+
assert len(data["coordinates"]) == 3
200+
# Verify all coordinates have x and y values
201+
for coord in data["coordinates"]:
202+
assert "x" in coord
203+
assert "y" in coord
204+
assert isinstance(coord["x"], (int, float))
205+
assert isinstance(coord["y"], (int, float))
206+
207+
97208
def test_healthcheck(client: TestClient):
98209
assert client.get("/health-check").text == '"success"'
99210

@@ -145,7 +256,7 @@ def test_gse_errors(client: TestClient):
145256
assert response.status_code == 422
146257

147258
# Test invalid JSON
148-
response = client.post("/gse2frame", data=b"invalid json")
259+
response = client.post("/gse2frame", content=b"invalid json")
149260
assert response.status_code == 422
150261

151262
# Test missing required field

0 commit comments

Comments
 (0)