Skip to content

Commit

Permalink
Update Flask JSON interfaces (#145)
Browse files Browse the repository at this point in the history
* Update Flask JSON interfaces

* formatting

* Unlock dependency
  • Loading branch information
thomas-bc authored Oct 11, 2023
1 parent b15cda5 commit 6a545e0
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 51 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
python_requires=">=3.7",
setup_requires=["setuptools_scm"],
install_requires=[
"flask>=1.1.2,<=2.2.3",
"flask>=2.2.0",
"flask_compress>=1.11",
"pyzmq>=24.0.1",
"pexpect>=4.8.0",
Expand Down
28 changes: 21 additions & 7 deletions src/fprime_gds/flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ def construct_app():
if "FP_FLASK_SETTINGS" in os.environ:
app.config.from_envvar("FP_FLASK_SETTINGS")

# JSON encoding setting must come before restful
app.json_encoder = fprime_gds.flask.json.GDSJsonEncoder
app.config["RESTFUL_JSON"] = {"cls": app.json_encoder}
# JSON encoding settings
app.json.default = fprime_gds.flask.json.default
app.config["RESTFUL_JSON"] = {"default": app.json.default}
# Standard pipeline creation
input_arguments = app.config["STANDARD_PIPELINE_ARGUMENTS"]
args_ns, _ = ParserBase.parse_args([StandardPipelineParser], "n/a", input_arguments, client=True)
args_ns, _ = ParserBase.parse_args(
[StandardPipelineParser], "n/a", input_arguments, client=True
)
pipeline = components.setup_pipelined_components(app.debug, args_ns)

# Restful API registration
Expand All @@ -83,7 +85,11 @@ def construct_app():
api.add_resource(
fprime_gds.flask.commands.CommandDictionary,
"/dictionary/commands",
resource_class_args=[pipeline.dictionaries.command_name, pipeline.dictionaries.project_version, pipeline.dictionaries.framework_version],
resource_class_args=[
pipeline.dictionaries.command_name,
pipeline.dictionaries.project_version,
pipeline.dictionaries.framework_version,
],
)
api.add_resource(
fprime_gds.flask.commands.CommandHistory,
Expand All @@ -98,7 +104,11 @@ def construct_app():
api.add_resource(
fprime_gds.flask.events.EventDictionary,
"/dictionary/events",
resource_class_args=[pipeline.dictionaries.event_id, pipeline.dictionaries.project_version, pipeline.dictionaries.framework_version],
resource_class_args=[
pipeline.dictionaries.event_id,
pipeline.dictionaries.project_version,
pipeline.dictionaries.framework_version,
],
)
api.add_resource(
fprime_gds.flask.events.EventHistory,
Expand All @@ -108,7 +118,11 @@ def construct_app():
api.add_resource(
fprime_gds.flask.channels.ChannelDictionary,
"/dictionary/channels",
resource_class_args=[pipeline.dictionaries.channel_id, pipeline.dictionaries.project_version, pipeline.dictionaries.framework_version],
resource_class_args=[
pipeline.dictionaries.channel_id,
pipeline.dictionaries.project_version,
pipeline.dictionaries.framework_version,
],
)
api.add_resource(
fprime_gds.flask.channels.ChannelHistory,
Expand Down
91 changes: 48 additions & 43 deletions src/fprime_gds/flask/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


def jsonify_base_type(input_type: Type[BaseType]) -> dict:
""" Turn a base type into a JSONable dictionary
"""Turn a base type into a JSONable dictionary
Convert a BaseType (the type, not an instance) into a jsonable dictionary. BaseTypes are converted by reading the
class properties (without __) and creating the object:
Expand All @@ -36,14 +36,17 @@ class properties
json-able dictionary representing the type
"""
assert issubclass(input_type, BaseType), "Failure to properly encode data"
members = getmembers(input_type, lambda value: not isroutine(value) and not isinstance(value, property))
members = getmembers(
input_type,
lambda value: not isroutine(value) and not isinstance(value, property),
)
jsonable_dict = {name: value for name, value in members if not name.startswith("_")}
jsonable_dict.update({"name": input_type.__name__})
return jsonable_dict


def getter_based_json(obj):
""" Converts objects to JSON via get_ methods
"""Converts objects to JSON via get_ methods
Template functions define a series of get_* methods whose return values need to be serialized. This function
handles that data.
Expand Down Expand Up @@ -80,7 +83,7 @@ def getter_based_json(obj):


def minimal_event(obj):
""" Minimal event encoding: time, id, display_text
"""Minimal event encoding: time, id, display_text
Events need time, id, display_text. No other information from the event is necessary for the display. This will
minimally encode the data for JSON.
Expand All @@ -95,7 +98,7 @@ def minimal_event(obj):


def minimal_channel(obj):
""" Minimal channel serialization: time, id, val, and display_text
"""Minimal channel serialization: time, id, val, and display_text
Minimally serializes channel values for use with the flask layer. This does away with any unnecessary data by
serializing only the id, value, and optional display text
Expand All @@ -106,11 +109,16 @@ def minimal_channel(obj):
Returns:
JSON compatible python anonymous type (dictionary)
"""
return {"time": obj.time, "id": obj.id, "val": obj.val_obj.val, "display_text": obj.display_text}
return {
"time": obj.time,
"id": obj.id,
"val": obj.val_obj.val,
"display_text": obj.display_text,
}


def minimal_command(obj):
""" Minimal command serialization: time, id, and args values
"""Minimal command serialization: time, id, and args values
Minimally serializes the command values for use with the flask layer. This prevents excess data by keeping the data
to the minimum instance data for commands including: time, opcode (id), and the value for args.
Expand All @@ -125,7 +133,7 @@ def minimal_command(obj):


def time_type(obj):
""" Time type serialization
"""Time type serialization
Serializes the time type into a JSON compatible object.
Expand All @@ -137,49 +145,46 @@ def time_type(obj):
"""
assert isinstance(obj, TimeType), "Incorrect type for serialization method"
return {
"base": obj.timeBase.value,
"context": obj.timeContext,
"seconds": obj.seconds,
"microseconds": obj.useconds
}
"base": obj.timeBase.value,
"context": obj.timeContext,
"seconds": obj.seconds,
"microseconds": obj.useconds,
}


def enum_json(obj):
""" Jsonify the python enums! """
"""Jsonify the python enums!"""
enum_dict = {"value": str(obj), "values": {}}
for enum_val in type(obj):
enum_dict["values"][str(enum_val)] = enum_val.value
return enum_dict


class GDSJsonEncoder(flask.json.JSONEncoder):
"""
Custom class used to handle GDS object to JSON
JSON_ENCODERS = {
ABCMeta: jsonify_base_type,
UUID: str,
ChData: minimal_channel,
EventData: minimal_event,
CmdData: minimal_command,
TimeType: time_type,
}


def default(obj):
"""
JSON_ENCODERS = {
ABCMeta: jsonify_base_type,
UUID: str,
ChData: minimal_channel,
EventData: minimal_event,
CmdData: minimal_command,
TimeType: time_type
}
Override the default JSON encoder to pull out a dictionary for our handled types for encoding with the default
encoder built into flask. This function must convert the given object into a JSON compatable python object (e.g.
using lists, dictionaries, strings, and primitive types).
def default(self, obj):
"""
Override the default JSON encoder to pull out a dictionary for our handled types for encoding with the default
encoder built into flask. This function must convert the given object into a JSON compatable python object (e.g.
using lists, dictionaries, strings, and primitive types).
:param obj: obj to encode
:return: JSON
"""
if type(obj) in self.JSON_ENCODERS:
return self.JSON_ENCODERS[type(obj)](obj)
if isinstance(obj, DataTemplate):
return getter_based_json(obj)
if isinstance(obj, Enum):
return enum_json(obj)
if isinstance(obj, ValueType):
return obj.val
return flask.json.JSONEncoder.default(self, obj)
:param obj: obj to encode
:return: JSON
"""
if type(obj) in JSON_ENCODERS:
return JSON_ENCODERS[type(obj)](obj)
if isinstance(obj, DataTemplate):
return getter_based_json(obj)
if isinstance(obj, Enum):
return enum_json(obj)
if isinstance(obj, ValueType):
return obj.val
return flask.json.provider.DefaultJSONProvider.default(obj)

0 comments on commit 6a545e0

Please sign in to comment.