Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions fiftyone/operators/_types/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,28 +96,35 @@ def stage(
return stage

@classmethod
def from_json(cls, json_list):
"""Loads the pipeline from a list of JSON/python dicts.
def from_json(cls, json_dict):
"""Loads the pipeline from a JSON/python dict.

Ex., [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage", ...},
...,
]
Ex., {
"stages": [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage"},
...,
]
}

Args:
json_list: a list of JSON / python dicts
json_dict: a JSON / python dict representation of the pipeline
"""
stages = [PipelineStage(**stage) for stage in json_list]
stages = [
PipelineStage(**stage) for stage in json_dict.get("stages") or []
]
return cls(stages=stages)
Comment on lines +99 to 115
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Handle legacy list payloads in Pipeline.from_json

Existing persisted documents still store pipelines as bare lists. The new implementation unconditionally calls json_dict.get(...), so deserializing those records now raises an AttributeError instead of rebuilding the pipeline. Please keep the old list shape supported while accepting the new dict form.

     @classmethod
     def from_json(cls, json_dict):
         """Loads the pipeline from a JSON/python dict.
@@
-        stages = [
-            PipelineStage(**stage) for stage in json_dict.get("stages") or []
-        ]
+        if json_dict is None:
+            stage_defs = []
+        elif isinstance(json_dict, list):
+            stage_defs = json_dict
+        elif isinstance(json_dict, dict):
+            stage_defs = json_dict.get("stages") or []
+        else:
+            raise TypeError(
+                "Pipeline.from_json expected a dict with a 'stages' key or a list"
+            )
+
+        stages = [PipelineStage(**stage) for stage in stage_defs]
         return cls(stages=stages)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def from_json(cls, json_dict):
"""Loads the pipeline from a JSON/python dict.
Ex., [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage", ...},
...,
]
Ex., {
"stages": [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage"},
...,
]
}
Args:
json_list: a list of JSON / python dicts
json_dict: a JSON / python dict representation of the pipeline
"""
stages = [PipelineStage(**stage) for stage in json_list]
stages = [
PipelineStage(**stage) for stage in json_dict.get("stages") or []
]
return cls(stages=stages)
@classmethod
def from_json(cls, json_dict):
"""Loads the pipeline from a JSON/python dict.
Ex., {
"stages": [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage"},
...,
]
}
Args:
json_dict: a JSON / python dict representation of the pipeline
"""
if json_dict is None:
stage_defs = []
elif isinstance(json_dict, list):
stage_defs = json_dict
elif isinstance(json_dict, dict):
stage_defs = json_dict.get("stages") or []
else:
raise TypeError(
"Pipeline.from_json expected a dict with a 'stages' key or a list"
)
stages = [PipelineStage(**stage) for stage in stage_defs]
return cls(stages=stages)
🤖 Prompt for AI Agents
In fiftyone/operators/_types/pipeline.py around lines 99 to 115, from_json
currently assumes json_dict is a dict and calls json_dict.get("stages"), which
breaks when persisted pipelines are stored as a bare list; update the method to
accept both legacy list payloads and the new dict shape by detecting if
json_dict is a list (or tuple) and using it directly as the stages list,
otherwise extract stages = json_dict.get("stages") or [] (also handle json_dict
being None), then build PipelineStage objects from that stages iterable and
return cls(stages=stages).


def to_json(self):
"""Converts the pipeline to list of JSON/python dicts.
"""Converts this pipeline to JSON/python dict representation

Ex., {
"stages": [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage"},
...,
]
}

Ex., [
{"operator_uri": "@voxel51/test/blah", "name": "my_stage", ...},
...,
]
Returns:
list of JSON / python dicts
JSON / python dict representation of the pipeline
"""
return [stage.to_json() for stage in self.stages]
return dataclasses.asdict(self)
21 changes: 3 additions & 18 deletions fiftyone/server/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,23 @@
|
"""

from json import JSONEncoder
import traceback
import typing as t
import logging

from bson import json_util
import numpy as np
from starlette.endpoints import HTTPEndpoint
from starlette.exceptions import HTTPException
from starlette.responses import JSONResponse, Response
from starlette.requests import Request

from fiftyone.core.utils import run_sync_task


class Encoder(JSONEncoder):
"""Custom JSON encoder that handles numpy types."""

def default(self, o):
if isinstance(o, np.floating):
return float(o)

if isinstance(o, np.integer):
return int(o)

return JSONEncoder.default(self, o)
from fiftyone.server import utils


async def create_response(response: dict):
"""Creates a JSON response from the given dictionary."""
return Response(
await run_sync_task(lambda: json_util.dumps(response, cls=Encoder)),
await run_sync_task(lambda: utils.json.dumps(response)),
headers={"Content-Type": "application/json"},
)

Expand All @@ -52,7 +37,7 @@ async def wrapper(
try:
body = await request.body()
payload = body.decode("utf-8")
data = json_util.loads(payload) if payload else {}
data = utils.json.loads(payload)
response = await func(endpoint, request, data, *args)
if isinstance(response, Response):
return response
Expand Down
Loading
Loading