Skip to content

Commit 80acc20

Browse files
some api changes
1 parent 12d386a commit 80acc20

File tree

10 files changed

+47
-30
lines changed

10 files changed

+47
-30
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
### Upcoming
2+
3+
#### Breaking changes
4+
5+
- Renamed `/api/is_local_access_point_active` to `/api/local_access_point` (now returns `{active: <bool>}`).
6+
- Consolidated experiment profile routes under `/api/experiment_profiles` and `/api/experiments/<experiment>/experiment_profiles/*`. Removed `/api/contrib/experiment_profiles` and `/api/experiment_profiles/running/experiments/<experiment>`. `PATCH` now targets `/api/experiment_profiles/<filename>`.
7+
8+
#### Enhancements
9+
10+
- Added `/api/units/<pioreactor_unit>/jobs/stop/experiments/<experiment>` to mirror worker stop-all behavior.
11+
112
### 26.2.3
213

314
#### Enhancements

core/pioreactor/web/api.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from pioreactor.pubsub import post_into
3636
from pioreactor.structs import CalibrationBase
3737
from pioreactor.structs import Dataset
38+
from pioreactor.utils.networking import is_using_local_access_point
3839
from pioreactor.utils.networking import resolve_to_address
3940
from pioreactor.utils.timing import current_utc_datetime
4041
from pioreactor.utils.timing import current_utc_timestamp
@@ -197,8 +198,12 @@ def get_models() -> ResponseReturnValue:
197198
"/workers/<pioreactor_unit>/jobs/stop/experiments/<experiment>",
198199
methods=["POST", "PATCH"],
199200
)
200-
def stop_all_jobs_on_worker_for_experiment(pioreactor_unit: str, experiment: str) -> ResponseReturnValue:
201-
"""Kills all jobs for worker assigned to experiment"""
201+
@api_bp.route(
202+
"/units/<pioreactor_unit>/jobs/stop/experiments/<experiment>",
203+
methods=["POST", "PATCH"],
204+
)
205+
def stop_all_jobs_on_unit_for_experiment(pioreactor_unit: str, experiment: str) -> ResponseReturnValue:
206+
"""Kills all jobs for worker or unit assigned to experiment"""
202207
if pioreactor_unit == UNIVERSAL_IDENTIFIER:
203208
broadcast_post_across_cluster("/unit_api/jobs/stop", json={"experiment": experiment})
204209
else:
@@ -2716,17 +2721,18 @@ def get_historical_config_for(filename: str) -> ResponseReturnValue:
27162721
return attach_cache_control(jsonify(configs_for_filename), max_age=15)
27172722

27182723

2719-
@api_bp.route("/is_local_access_point_active", methods=["GET"])
2720-
def is_local_access_point_active() -> ResponseReturnValue:
2724+
@api_bp.route("/local_access_point", methods=["GET"])
2725+
def get_local_access_point() -> ResponseReturnValue:
27212726
return attach_cache_control(
2722-
jsonify({"result": os.path.isfile("/boot/firmware/local_access_point")}), max_age=10_000
2727+
jsonify({"active": is_using_local_access_point()}),
2728+
max_age=10_000,
27232729
)
27242730

27252731

27262732
### experiment profiles
27272733

27282734

2729-
@api_bp.route("/experiment_profiles/running/experiments/<experiment>", methods=["GET"])
2735+
@api_bp.route("/experiments/<experiment>/experiment_profiles/running", methods=["GET"])
27302736
def get_running_profiles(experiment: str) -> ResponseReturnValue:
27312737
jobs = query_temp_local_metadata_db(
27322738
"""
@@ -2771,7 +2777,7 @@ def get_recent_experiment_profile_runs(experiment: str) -> ResponseReturnValue:
27712777
return attach_cache_control(jsonify(recent_runs), max_age=5)
27722778

27732779

2774-
@api_bp.route("/contrib/experiment_profiles", methods=["POST"])
2780+
@api_bp.route("/experiment_profiles", methods=["POST"])
27752781
def create_experiment_profile() -> ResponseReturnValue:
27762782
body = request.get_json()
27772783
experiment_profile_body = body["body"]
@@ -2815,11 +2821,11 @@ def create_experiment_profile() -> ResponseReturnValue:
28152821
return {"status": "success"}, 200
28162822

28172823

2818-
@api_bp.route("/contrib/experiment_profiles", methods=["PATCH"])
2819-
def update_experiment_profile() -> ResponseReturnValue:
2824+
@api_bp.route("/experiment_profiles/<filename>", methods=["PATCH"])
2825+
def update_experiment_profile(filename: str) -> ResponseReturnValue:
28202826
body = request.get_json()
28212827
experiment_profile_body = body["body"]
2822-
experiment_profile_filename = Path(body["filename"]).name
2828+
experiment_profile_filename = Path(filename).name
28232829

28242830
# verify content
28252831
try:
@@ -2853,7 +2859,7 @@ def update_experiment_profile() -> ResponseReturnValue:
28532859
return {"status": "success"}, 200
28542860

28552861

2856-
@api_bp.route("/contrib/experiment_profiles", methods=["GET"])
2862+
@api_bp.route("/experiment_profiles", methods=["GET"])
28572863
def get_experiment_profiles() -> ResponseReturnValue:
28582864
try:
28592865
profile_path = Path(os.environ["DOT_PIOREACTOR"]) / "experiment_profiles"
@@ -2890,7 +2896,7 @@ def get_experiment_profiles() -> ResponseReturnValue:
28902896
abort_with(400, str(e))
28912897

28922898

2893-
@api_bp.route("/contrib/experiment_profiles/<filename>", methods=["GET"])
2899+
@api_bp.route("/experiment_profiles/<filename>", methods=["GET"])
28942900
def get_experiment_profile(filename: str) -> ResponseReturnValue:
28952901
file = Path(filename).name
28962902
try:
@@ -2908,7 +2914,7 @@ def get_experiment_profile(filename: str) -> ResponseReturnValue:
29082914
abort_with(404, str(e))
29092915

29102916

2911-
@api_bp.route("/contrib/experiment_profiles/<filename>", methods=["DELETE"])
2917+
@api_bp.route("/experiment_profiles/<filename>", methods=["DELETE"])
29122918
def delete_experiment_profile(filename: str) -> ResponseReturnValue:
29132919
file = Path(filename).name
29142920
try:

core/pioreactor/web/mcp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def get_experiment_profiles() -> dict:
382382
383383
List available experiment profiles (filename, fullpath, and parsed metadata).
384384
"""
385-
return get_from_leader("/api/contrib/experiment_profiles")
385+
return get_from_leader("/api/experiment_profiles")
386386

387387

388388
@mcp.tool()

core/tests/web/test_app.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,16 +346,16 @@ def test_get_config_rejects_non_ini(client) -> None:
346346

347347
def test_create_experiment_profile_invalid_filename_returns_400(client) -> None:
348348
response = client.post(
349-
"/api/contrib/experiment_profiles",
349+
"/api/experiment_profiles",
350350
json={"body": "experiment_profile_name: demo", "filename": "bad?name.yaml"},
351351
)
352352
assert response.status_code == 400
353353

354354

355355
def test_update_experiment_profile_invalid_filename_returns_400(client) -> None:
356356
response = client.patch(
357-
"/api/contrib/experiment_profiles",
358-
json={"body": "experiment_profile_name: demo", "filename": "bad?name.yaml"},
357+
"/api/experiment_profiles/bad:name.yaml",
358+
json={"body": "experiment_profile_name: demo"},
359359
)
360360
assert response.status_code == 400
361361

core/tests/web/test_mcp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,5 +124,5 @@ def test_endpoints_exist_in_api_and_unit_api(app) -> None:
124124
"/api/workers/<pioreactor_unit>/jobs/settings/job_name/<job_name>/experiments/<experiment>" in routes
125125
)
126126
assert "/api/experiments/<experiment>/recent_logs" in routes
127-
assert "/api/contrib/experiment_profiles" in routes
127+
assert "/api/experiment_profiles" in routes
128128
assert "/api/units/<pioreactor_unit>/configuration" in routes

frontend/src/CreateExperimentProfile.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ metadata:
9595
}
9696

9797
setIsError(false);
98-
fetch("/api/contrib/experiment_profiles", {
98+
fetch("/api/experiment_profiles", {
9999
method: "POST",
100100
body: JSON.stringify({ body: code, filename: filename + '.yaml' }),
101101
headers: {

frontend/src/EditExperimentProfile.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ const EditExperimentProfilesContent = ({ initialCode, profileFilename }) => {
8181
const saveCurrentCode = () => {
8282

8383
setIsError(false);
84-
fetch("/api/contrib/experiment_profiles", {
84+
fetch(`/api/experiment_profiles/${encodeURIComponent(profileFilename)}`, {
8585
method: "PATCH",
86-
body: JSON.stringify({ body: code, filename: profileFilename }),
86+
body: JSON.stringify({ body: code }),
8787
headers: {
8888
'Accept': 'application/json',
8989
'Content-Type': 'application/json'
@@ -199,7 +199,7 @@ function ProfilesContainer(){
199199
const [openCapabilities, setOpenCapabilities] = React.useState(false)
200200

201201
React.useEffect(() => {
202-
fetch(`/api/contrib/experiment_profiles/${profileFilename}`, {
202+
fetch(`/api/experiment_profiles/${encodeURIComponent(profileFilename)}`, {
203203
method: "GET",
204204
}).then(res => {
205205
if (res.ok) {

frontend/src/Profiles.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function RunExperimentProfilesContent({
7575
cancellationButtonProps: { color: "secondary" },
7676
})
7777
.then(() => {
78-
fetch(`/api/contrib/experiment_profiles/${selectedExperimentProfile}`, {
78+
fetch(`/api/experiment_profiles/${selectedExperimentProfile}`, {
7979
method: "DELETE",
8080
}).then(res => {
8181
if (res.ok) {
@@ -89,7 +89,7 @@ function RunExperimentProfilesContent({
8989
const getSourceAndView = () => {
9090
// fetch the raw file content only if we are about to toggle into “view source”
9191
if (!viewSource) {
92-
fetch(`/api/contrib/experiment_profiles/${selectedExperimentProfile}`, {
92+
fetch(`/api/experiment_profiles/${selectedExperimentProfile}`, {
9393
method: "GET",
9494
}).then(res => {
9595
if (res.ok) {
@@ -106,7 +106,7 @@ function RunExperimentProfilesContent({
106106
if (!selectedExperimentProfile) {
107107
return;
108108
}
109-
fetch(`/api/contrib/experiment_profiles/${selectedExperimentProfile}`, { method: 'GET' })
109+
fetch(`/api/experiment_profiles/${selectedExperimentProfile}`, { method: 'GET' })
110110
.then(res => {
111111
if (!res.ok) {
112112
throw new Error('Failed to fetch profile');
@@ -419,7 +419,7 @@ function Profiles(props) {
419419
}, [experimentMetadata.experiment]);
420420

421421
React.useEffect(() => {
422-
fetch("/api/contrib/experiment_profiles")
422+
fetch("/api/experiment_profiles")
423423
.then(response => response.json())
424424
.then(profiles => {
425425
// shape: [ {file: "...", experimentProfile: {...}}, ... ]

frontend/src/components/SideNavAndHeader.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,12 @@ export default function SideNavAndHeader() {
186186

187187
React.useEffect(() => {
188188
async function getLAP() {
189-
await fetch("/api/is_local_access_point_active")
189+
await fetch("/api/local_access_point")
190190
.then((response) => {
191-
return response.text();
191+
return response.json();
192192
})
193193
.then((data) => {
194-
setLAP(data === "true")
194+
setLAP(Boolean(data?.active))
195195
});
196196
}
197197

frontend/src/providers/RunningProfilesContext.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function RunningProfilesProvider({ children, experiment }) {
2929
setLoading(true);
3030
try {
3131
const response = await fetch(
32-
`/api/experiment_profiles/running/experiments/${experiment}`
32+
`/api/experiments/${encodeURIComponent(experiment)}/experiment_profiles/running`
3333
);
3434
if (!response.ok) {
3535
throw new Error(`Failed to fetch running profiles: ${response.statusText}`);

0 commit comments

Comments
 (0)