From fb8024be8be75c62b83c00eafb3df5a617b4c171 Mon Sep 17 00:00:00 2001 From: Silver Valdvee Date: Tue, 24 Oct 2023 19:34:56 +0300 Subject: [PATCH 1/2] Update Flask and pyinstaller, replace Flask's jsonify Flask's jsonify is no longer functional, seems to be bugged. --- dev-requirements.txt | 2 +- requirements.txt | 2 +- yukon/electron_folder/main.js | 4 +-- yukon/main.py | 11 ++++++- yukon/server.py | 17 +++++----- yukon/services/api.py | 3 +- yukon/services/enhanced_json_encoder.py | 43 +++++++++++++++++++++++++ yukon/web/main/main.js | 8 +++++ 8 files changed, 76 insertions(+), 14 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index c1feab3e..0630d44a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,4 @@ -pyinstaller==5.6.* +pyinstaller==5.13.2 parsimonious nox==2022.8.* types-PyYAML diff --git a/requirements.txt b/requirements.txt index df29115c..c2a48d26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ setuptools numpy importlib_resources; python_version < "3.7" proxy_tools -Flask==2.2.* +Flask==3.* python-can==4.* requests websockets==10.* diff --git a/yukon/electron_folder/main.js b/yukon/electron_folder/main.js index 3e4a58e9..d292a419 100644 --- a/yukon/electron_folder/main.js +++ b/yukon/electron_folder/main.js @@ -202,7 +202,7 @@ let menuTemplate = [ app.whenReady().then(() => { // Send a GET request to http://locahost:5000/api/announce_running_in_electron // to announce that the app is running in electron - http.get(`http://localhost:${yukon_server_port}/api/announce_running_in_electron`, (resp) => { + http.get(`http://127.0.0.1:${yukon_server_port}/api/announce_running_in_electron`, (resp) => { }); console.log("Announcing that running in electron") ipcMain.handle('dialog:openPath', handlePathOpen); @@ -232,7 +232,7 @@ app.whenReady().then(() => { app.on('window-all-closed', () => { if (process.platform !== 'darwin') { - http.get(`http://localhost:${yukon_server_port}/api/close_yukon`, (resp) => { + http.get(`http://127.0.0.1:${yukon_server_port}/api/close_yukon`, (resp) => { }); app.quit(); } diff --git a/yukon/main.py b/yukon/main.py index d8dad778..d0253d37 100644 --- a/yukon/main.py +++ b/yukon/main.py @@ -2,6 +2,7 @@ import socket import threading import time +import traceback import webbrowser import typing from typing import Optional, Any @@ -172,6 +173,12 @@ def run_webbrowser_open() -> None: def open_webbrowser(state: GodState) -> None: + # Make sure the splash screen is not blocking the screen any more. + try: + import pyi_splash + pyi_splash.close() + except: + pass while not state.gui.is_port_decided: sleep(0.5) logger.info("Opening web browser") @@ -241,8 +248,10 @@ def run_server(state: GodState) -> None: continue state.gui.is_port_decided = True try: - server.run(host="0.0.0.0", port=state.gui.server_port, threaded=True) + server.run(host="127.0.0.1", port=state.gui.server_port, threaded=True) except: # pylint: disable=bare-except + tb = traceback.format_exc() + logger.critical(str(tb)) logger.exception("Server was unable to start or crashed.") diff --git a/yukon/server.py b/yukon/server.py index d3ec409a..a3526dc8 100644 --- a/yukon/server.py +++ b/yukon/server.py @@ -6,14 +6,13 @@ from inspect import signature import sys -from flask import Flask, Response, jsonify, request -from flask.blueprints import T_after_request +from flask import Flask, Response, request from werkzeug.serving import WSGIRequestHandler from yukon.domain.subject_specifier import SubjectSpecifier from yukon.services._dumper import Dumper -from yukon.services.enhanced_json_encoder import EnhancedJSONEncoder +from yukon.services.enhanced_json_encoder import EnhancedJSONEncoder, jsonify from yukon.domain.god_state import GodState from yukon.services.api import Api @@ -28,14 +27,13 @@ server = Flask(__name__, static_folder=gui_dir, template_folder=gui_dir, static_url_path="") server.config["SEND_FILE_MAX_AGE_DEFAULT"] = 1 # disable caching -server.json_encoder = EnhancedJSONEncoder WSGIRequestHandler.protocol_version = "HTTP/1.1" our_token = "ABC" logger = logging.getLogger(__name__) @server.after_request -def add_header(response: T_after_request) -> T_after_request: +def add_header(response): response.headers["Cache-Control"] = "no-store" response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS,POST,PUT" @@ -48,10 +46,13 @@ def make_landing_and_bridge(state: GodState, api: Api) -> None: def landing_and_bridge(path: str) -> typing.Any: _object: typing.Any = {"arguments": []} try: - _object = request.get_json() + request_data: bytes = request.get_data() + request_data_string: str = request_data.decode("utf-8") + _object = json.loads(request_data_string) + print(_object) except Exception as _: # pylint: disable=broad-except - pass - # logger.warning("There was no json data attached") + tb = traceback.format_exc() + logger.critical(tb) try: found_method = getattr(api, path) except Exception: # pylint: disable=broad-except diff --git a/yukon/services/api.py b/yukon/services/api.py index 9274f300..42847c48 100644 --- a/yukon/services/api.py +++ b/yukon/services/api.py @@ -28,7 +28,8 @@ except ImportError: from yaml import Loader # type: ignore import websockets -from flask import jsonify, Response +from flask import Response +from yukon.services.enhanced_json_encoder import jsonify from pycyphal.presentation.subscription_synchronizer import get_local_reception_timestamp from pycyphal.presentation.subscription_synchronizer.monotonic_clustering import MonotonicClusteringSynchronizer diff --git a/yukon/services/enhanced_json_encoder.py b/yukon/services/enhanced_json_encoder.py index 4ec1115a..bfa7a116 100644 --- a/yukon/services/enhanced_json_encoder.py +++ b/yukon/services/enhanced_json_encoder.py @@ -5,6 +5,8 @@ from json.encoder import encode_basestring_ascii, encode_basestring, c_make_encoder, _make_iterencode # type: ignore import typing from uuid import UUID +from flask import Response +from flask.json.provider import DefaultJSONProvider as FlaskJSONProvider import pycyphal @@ -30,6 +32,9 @@ _logger = logging.getLogger(__name__) +# Flask has some broken code around this, making my own +def jsonify(obj: typing.Any): + return Response(json.dumps(obj, cls=EnhancedJSONEncoder), mimetype="application/json") class EnhancedJSONEncoder(json.JSONEncoder): def default(self, o: typing.Any) -> typing.Any: @@ -302,3 +307,41 @@ def default(self, o: typing.Any) -> typing.Any: # Return a dict that doesn't contain keys that start with __id__ return {k: v for k, v in o.value.items() if not k.startswith("__id__")} return super().default(o) + + +class FlaskEnhancedJSONEncoder(FlaskJSONProvider): + # Implement the FlaskJSONProvider API by using the Python JSON API + def dumps(self, obj: typing.Any, **kwargs: typing.Any) -> str: + """Serialize data as JSON. + + :param obj: The data to serialize. + :param kwargs: May be passed to the underlying JSON library. + """ + return json.dumps(obj, cls=EnhancedJSONEncoder, **kwargs) + + + def dump(self, obj: typing.Any, fp: typing.IO[str], **kwargs: typing.Any) -> None: + """Serialize data as JSON and write to a file. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: May be passed to the underlying JSON library. + """ + json.dump(obj, fp, cls=EnhancedJSONEncoder, **kwargs) + + def loads(self, s: str | bytes, **kwargs: typing.Any) -> typing.Any: + """Deserialize data as JSON. + + :param s: Text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return json.loads(s, cls=EnhancedJSONEncoder, **kwargs) + + def load(self, fp: typing.IO[typing.AnyStr], **kwargs: typing.Any) -> typing.Any: + """Deserialize data as JSON read from a file. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return json.load(fp, cls=EnhancedJSONEncoder, **kwargs) \ No newline at end of file diff --git a/yukon/web/main/main.js b/yukon/web/main/main.js index 55a4c220..d914ac0a 100644 --- a/yukon/web/main/main.js +++ b/yukon/web/main/main.js @@ -132,6 +132,14 @@ window.console = new Proxy(old_console, { } yukon_state.addLocalMessage = function (message, severity) { + if(message === undefined) { + console.error("Message is undefined"); + return; + } + if (severity === undefined) { + console.error("Severity is undefined"); + return; + } zubax_api.add_local_message(message, severity); } yukon_state.addLocalMessage("Press CTRL+SPACE to maximize the panel under your mouse", 30); From c8dabdb121b429572483c0c5e6781d611af14b00 Mon Sep 17 00:00:00 2001 From: Silver Valdvee Date: Wed, 22 Nov 2023 18:07:53 +0200 Subject: [PATCH 2/2] Two different fixes, one for typing and one for an issue --- yukon/services/enhanced_json_encoder.py | 2 +- yukon/web/modules/context-menu.module.js | 6 ++++++ .../panels/monitor2/monitor2.module.js | 21 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/yukon/services/enhanced_json_encoder.py b/yukon/services/enhanced_json_encoder.py index bfa7a116..0e042f78 100644 --- a/yukon/services/enhanced_json_encoder.py +++ b/yukon/services/enhanced_json_encoder.py @@ -33,7 +33,7 @@ _logger = logging.getLogger(__name__) # Flask has some broken code around this, making my own -def jsonify(obj: typing.Any): +def jsonify(obj: typing.Any) -> Response: return Response(json.dumps(obj, cls=EnhancedJSONEncoder), mimetype="application/json") class EnhancedJSONEncoder(json.JSONEncoder): diff --git a/yukon/web/modules/context-menu.module.js b/yukon/web/modules/context-menu.module.js index 2e00b5dd..6d6291c5 100644 --- a/yukon/web/modules/context-menu.module.js +++ b/yukon/web/modules/context-menu.module.js @@ -563,6 +563,12 @@ export function make_context_menus(yukon_state) { click: async (e, elementOpenedOn) => { const portNr = parseInt(elementOpenedOn.getAttribute("data-port")); const datatypes = await getDatatypesForPort(portNr, "pub", yukon_state); + if(!datatypes[0]) { + console.error(``` + Please make sure that registers exist for data type names for publication/subscription/client/server. + https://github.com/OpenCyphal/public_regulated_data_types/blob/935973babe11755d8070e67452b3508b4b6833e2/uavcan/register/384.Access.1.0.dsdl#L154-L162 + ```); + } const response = await yukon_state.zubax_apij.make_simple_publisher_with_datatype_and_port_id(datatypes[0], portNr); const portType = elementOpenedOn.getAttribute("data-port-type"); // sub or pub or cln or srv if (response && response.success) { diff --git a/yukon/web/modules/panels/monitor2/monitor2.module.js b/yukon/web/modules/panels/monitor2/monitor2.module.js index 7f5c4c11..29fcffe2 100644 --- a/yukon/web/modules/panels/monitor2/monitor2.module.js +++ b/yukon/web/modules/panels/monitor2/monitor2.module.js @@ -477,7 +477,16 @@ async function update_monitor2(containerElement, monitor2Div, yukon_state, force currentLinkDsdlDatatype = currentLinkObject.name + ":" + currentLinkDsdlDatatype; } } else { - currentLinkDsdlDatatype = fixed_datatype_full || "There is no info about this link"; + currentLinkDsdlDatatype = fixed_datatype_full; + } + if(currentLinkDsdlDatatype) { + currentLinkDsdlDatatype = fixed_datatype_full; + } else { + // Handling a special case where the developer of a Cyphal node hasn't got registers set up for data type names + // https://github.com/OpenCyphal/public_regulated_data_types/blob/935973babe11755d8070e67452b3508b4b6833e2/uavcan/register/384.Access.1.0.dsdl#L154-L162 + // https://forum.opencyphal.org/t/developing-pico-node-using-yukon/1978 + // addHorizontalElements is going to give this a message and also make it clickable and hinted so that the link can be had from the label. + currentLinkDsdlDatatype = null; } let isLast = false; // If this is the last iteration of the loop, set a variable to true @@ -989,6 +998,16 @@ function addHorizontalElements(monitor2Div, matchingPort, currentLinkDsdlDatatyp if (currentLinkDsdlDatatype.endsWith(".Response") || currentLinkDsdlDatatype.endsWith(".Request")) { currentLinkDsdlDatatype = currentLinkDsdlDatatype.replace(".Response", "").replace(".Request", ""); } + if(!currentLinkDsdlDatatype) { + horizontal_line_label.addEventListener("mousedown", () => { + console.error(``` + Please make sure that registers exist for data type names for publication/subscription/client/server. + https://github.com/OpenCyphal/public_regulated_data_types/blob/935973babe11755d8070e67452b3508b4b6833e2/uavcan/register/384.Access.1.0.dsdl#L154-L162 + ```); + }); + horizontal_line_label.title = "Click for more information in console." + currentLinkDsdlDatatype = "Missing data type name registers."; + } horizontal_line_label.innerHTML = currentLinkDsdlDatatype; horizontal_line_label.style.zIndex = "3"; // horizontal_line_label.style.backgroundColor = settings["LinkLabelColor"];