Example #1
0
class EmcExe_Qt(EmcExe):
    """ PySide2 implementation of the EmcExec """
    def __init__(self, *args, **kargs):
        super().__init__(*args, **kargs)

        self._proc = QProcess()
        self._proc.errorOccurred.connect(self._error_cb)

        if self._done_cb:
            self._proc.finished.connect(self._finished_cb)

        if self._grab_output:
            self._proc.readyReadStandardOutput.connect(self._stdout_cb)

        if self._params:
            self._proc.start(self._cmd, self._params)
        else:
            self._proc.start(self._cmd)

    def delete(self) -> None:
        super().delete()
        if self._proc and self._proc.state() == QProcess.Running:
            self._proc.kill()
        self._proc = None

    def _finished_cb(self, exit_code):
        self._call_user_callback(exit_code)

    def _error_cb(self, error):
        if self._proc and not self.deleted:
            self._call_user_callback(-1)

    def _stdout_cb(self):
        if self._proc and not self.deleted:
            self._out_buffer.append(self._proc.readAllStandardOutput().data())
Example #2
0
class MediaText(Media):

    def __init__(self, media, parent_widget):
        super(MediaText, self).__init__(media, parent_widget)
        self.widget = QWidget(parent_widget)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        self.rect = self.widget.geometry()

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if  not self.is_started():
            self.started_signal.emit()
        super(MediaText, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if  float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        #---- kong ----
        path = f'file:///home/pi/rdtone/urd/content/{self.layout_id}_{self.region_id}_{self.id}.html'
        
        print(path)
        
        l = str(self.rect.left())
        t = str(self.rect.top())
        w = str(self.rect.width())
        h = str(self.rect.height())
        s = f'--window-size={w},{h}'
        p = f'--window-position={l},{t}'
        args = [
            '--kiosk', s, p, path
            #l, t, w, h, path
        ]
        self.process.start('chromium-browser', args)
        #self.process.start('./xWeb', args)
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ----
        if  not self.widget:
            return False
        if  self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if  self.process.state() == QProcess.ProcessState.Running:
            #---- kill process ----
            os.system('pkill chromium')
            #os.system('pkill xWeb')
            #----
            self.process.waitForFinished()
            self.process.close()
        super(MediaText, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
Example #3
0
class MediaVideo(Media):

    def __init__(self, media, parent_widget):
        super(MediaVideo, self).__init__(media, parent_widget)
        self.widget = QWidget(parent_widget)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        #---- kong ---- for RPi
        self.rect = media['geometry']
        #----

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if  not self.is_started():
            self.started_signal.emit()
        super(MediaVideo, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if  float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        uri = self.options['uri']
        path = f'content/{uri}'
        #---- kong ---- for RPi
        left, top, right, bottom = self.rect.getCoords()
        rect = f'{left},{top},{right},{bottom}'
        args = [ '--win', rect, '--no-osd', '--layer', self.zindex, path ]
        self.process.start('omxplayer.bin', args)
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ---- for RPi
        if  not self.widget:
            return False
        if  self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if  self.process.state() == QProcess.ProcessState.Running:
            self.process.write(b'q')
            self.process.waitForFinished()
            self.process.close()
        super(MediaVideo, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
Example #4
0
class WebMediaView(MediaView):
    def __init__(self, media, parent):
        super(WebMediaView, self).__init__(media, parent)
        self.widget = QWidget(parent)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        self.rect = self.widget.geometry()

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if not self.is_started():
            self.started_signal.emit()
        super(WebMediaView, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        #---- kong ----
        url = self.options['uri']
        args = [
            str(self.rect.left()),
            str(self.rect.top()),
            str(self.rect.width()),
            str(self.rect.height()),
            QUrl.fromPercentEncoding(QByteArray(url.encode('utf-8')))
        ]
        #self.process.start('dist/web.exe', args) # for windows
        #self.process.start('./dist/web', args) # for RPi
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ----
        if not self.widget:
            return False
        if self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if self.process.state() == QProcess.ProcessState.Running:
            #---- kill process ----
            self.process.terminate()  # for windows
            self.process.kill()  # for linux
            #os.system('pkill web') # for RPi
            #----
            self.process.waitForFinished()
            self.process.close()
        super(WebMediaView, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
Example #5
0
class Importer(ProjectItem):
    def __init__(self, name, description, mappings, x, y, toolbox, project, logger, cancel_on_error=True):
        """Importer class.

        Args:
            name (str): Project item name
            description (str): Project item description
            mappings (list): List where each element contains two dicts (path dict and mapping dict)
            x (float): Initial icon scene X coordinate
            y (float): Initial icon scene Y coordinate
            toolbox (ToolboxUI): QMainWindow instance
            project (SpineToolboxProject): the project this item belongs to
            logger (LoggerInterface): a logger instance
            cancel_on_error (bool): if True the item's execution will stop on import error
       """
        super().__init__(name, description, x, y, project, logger)
        # Make logs subdirectory for this item
        self._toolbox = toolbox
        self.logs_dir = os.path.join(self.data_dir, "logs")
        try:
            create_dir(self.logs_dir)
        except OSError:
            self._logger.msg_error.emit(f"[OSError] Creating directory {self.logs_dir} failed. Check permissions.")
        # Variables for saving selections when item is (de)activated
        if not mappings:
            mappings = list()
        # convert table_types and table_row_types keys to int since json always has strings as keys.
        for _, mapping in mappings:
            table_types = mapping.get("table_types", {})
            mapping["table_types"] = {
                table_name: {int(col): t for col, t in col_types.items()}
                for table_name, col_types in table_types.items()
            }
            table_row_types = mapping.get("table_row_types", {})
            mapping["table_row_types"] = {
                table_name: {int(row): t for row, t in row_types.items()}
                for table_name, row_types in table_row_types.items()
            }
        # Convert serialized paths to absolute in mappings
        self.settings = self.deserialize_mappings(mappings, self._project.project_dir)
        # self.settings is now a dictionary, where elements have the absolute path as the key and the mapping as value
        self.cancel_on_error = cancel_on_error
        self.resources_from_downstream = list()
        self.file_model = QStandardItemModel()
        self.importer_process = None
        self.all_files = []  # All source files
        self.unchecked_files = []  # Unchecked source files
        # connector class
        self._preview_widget = {}  # Key is the filepath, value is the ImportPreviewWindow instance

    @staticmethod
    def item_type():
        """See base class."""
        return "Importer"

    @staticmethod
    def category():
        """See base class."""
        return "Importers"

    @Slot(QStandardItem, name="_handle_file_model_item_changed")
    def _handle_file_model_item_changed(self, item):
        if item.checkState() == Qt.Checked:
            self.unchecked_files.remove(item.text())
            self._logger.msg.emit(f"<b>{self.name}:</b> Source file '{item.text()}' will be processed at execution.")
        elif item.checkState() != Qt.Checked:
            self.unchecked_files.append(item.text())
            self._logger.msg.emit(
                f"<b>{self.name}:</b> Source file '{item.text()}' will *NOT* be processed at execution."
            )

    def make_signal_handler_dict(self):
        """Returns a dictionary of all shared signals and their handlers.
        This is to enable simpler connecting and disconnecting."""
        s = super().make_signal_handler_dict()
        s[self._properties_ui.toolButton_open_dir.clicked] = lambda checked=False: self.open_directory()
        s[self._properties_ui.pushButton_import_editor.clicked] = self._handle_import_editor_clicked
        s[self._properties_ui.treeView_files.doubleClicked] = self._handle_files_double_clicked
        return s

    def activate(self):
        """Restores selections, cancel on error checkbox and connects signals."""
        self._properties_ui.cancel_on_error_checkBox.setCheckState(Qt.Checked if self.cancel_on_error else Qt.Unchecked)
        self.restore_selections()
        super().connect_signals()

    def deactivate(self):
        """Saves selections and disconnects signals."""
        self.save_selections()
        if not super().disconnect_signals():
            logging.error("Item %s deactivation failed.", self.name)
            return False
        return True

    def restore_selections(self):
        """Restores selections into shared widgets when this project item is selected."""
        self._properties_ui.label_name.setText(self.name)
        self._properties_ui.treeView_files.setModel(self.file_model)
        self.file_model.itemChanged.connect(self._handle_file_model_item_changed)

    def save_selections(self):
        """Saves selections in shared widgets for this project item into instance variables."""
        self._properties_ui.treeView_files.setModel(None)
        self.file_model.itemChanged.disconnect(self._handle_file_model_item_changed)

    def update_name_label(self):
        """Update Importer properties tab name label. Used only when renaming project items."""
        self._properties_ui.label_name.setText(self.name)

    @Slot(bool, name="_handle_import_editor_clicked")
    def _handle_import_editor_clicked(self, checked=False):
        """Opens Import editor for the file selected in list view."""
        index = self._properties_ui.treeView_files.currentIndex()
        self.open_import_editor(index)

    @Slot("QModelIndex", name="_handle_files_double_clicked")
    def _handle_files_double_clicked(self, index):
        """Opens Import editor for the double clicked index."""
        self.open_import_editor(index)

    def open_import_editor(self, index):
        """Opens Import editor for the given index."""
        importee = index.data()
        if importee is None:
            self._logger.msg_error.emit("Please select a source file from the list first.")
            return
        if not os.path.exists(importee):
            self._logger.msg_error.emit(f"Invalid path: {importee}")
            return
        # Raise current form for the selected file if any
        preview_widget = self._preview_widget.get(importee, None)
        if preview_widget:
            if preview_widget.windowState() & Qt.WindowMinimized:
                # Remove minimized status and restore window with the previous state (maximized/normal state)
                preview_widget.setWindowState(preview_widget.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
                preview_widget.activateWindow()
            else:
                preview_widget.raise_()
            return
        # Create a new form for the selected file
        settings = self.get_settings(importee)
        # Try and get connector from settings
        source_type = settings.get("source_type", None)
        if source_type is not None:
            connector = _CONNECTOR_NAME_TO_CLASS[source_type]
        else:
            # Ask user
            connector = self.get_connector(importee)
            if not connector:
                # Aborted by the user
                return
        self._logger.msg.emit(f"Opening Import editor for file: {importee}")
        preview_widget = self._preview_widget[importee] = ImportPreviewWindow(
            self, importee, connector, settings, self._toolbox
        )
        preview_widget.settings_updated.connect(lambda s, importee=importee: self.save_settings(s, importee))
        preview_widget.connection_failed.connect(lambda m, importee=importee: self._connection_failed(m, importee))
        preview_widget.destroyed.connect(lambda o=None, importee=importee: self._preview_destroyed(importee))
        preview_widget.start_ui()

    def get_connector(self, importee):
        """Shows a QDialog to select a connector for the given source file.
        Mimics similar routine in `spine_io.widgets.import_widget.ImportDialog`

        Args:
            importee (str): Path to file acting as an importee

        Returns:
            Asynchronous data reader class for the given importee
        """
        connector_list = [CSVConnector, ExcelConnector, GdxConnector]  # add others as needed
        connector_names = [c.DISPLAY_NAME for c in connector_list]
        dialog = QDialog(self._toolbox)
        dialog.setLayout(QVBoxLayout())
        connector_list_wg = QListWidget()
        connector_list_wg.addItems(connector_names)
        # Set current item in `connector_list_wg` based on file extension
        _filename, file_extension = os.path.splitext(importee)
        if file_extension.lower().startswith(".xls"):
            row = connector_list.index(ExcelConnector)
        elif file_extension.lower() == ".csv":
            row = connector_list.index(CSVConnector)
        elif file_extension.lower() == ".gdx":
            row = connector_list.index(GdxConnector)
        else:
            row = None
        if row:
            connector_list_wg.setCurrentRow(row)
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.button(QDialogButtonBox.Ok).clicked.connect(dialog.accept)
        button_box.button(QDialogButtonBox.Cancel).clicked.connect(dialog.reject)
        connector_list_wg.doubleClicked.connect(dialog.accept)
        dialog.layout().addWidget(connector_list_wg)
        dialog.layout().addWidget(button_box)
        _dirname, filename = os.path.split(importee)
        dialog.setWindowTitle("Select connector for '{}'".format(filename))
        answer = dialog.exec_()
        if answer:
            row = connector_list_wg.currentIndex().row()
            return connector_list[row]

    def select_connector_type(self, index):
        """Opens dialog to select connector type for the given index."""
        importee = index.data()
        connector = self.get_connector(importee)
        if not connector:
            # Aborted by the user
            return
        settings = self.get_settings(importee)
        settings["source_type"] = connector.__name__

    def _connection_failed(self, msg, importee):
        self._logger.msg.emit(msg)
        preview_widget = self._preview_widget.pop(importee, None)
        if preview_widget:
            preview_widget.close()

    def get_settings(self, importee):
        """Returns the mapping dictionary for the file in given path.

        Args:
            importee (str): Absolute path to a file, whose mapping is queried

        Returns:
            dict: Mapping dictionary for the requested importee or an empty dict if not found
        """
        importee_settings = None
        for p in self.settings:
            if p == importee:
                importee_settings = self.settings[p]
        if not importee_settings:
            return {}
        return importee_settings

    def save_settings(self, settings, importee):
        """Updates an existing mapping or adds a new mapping
         (settings) after closing the import preview window.

        Args:
            settings (dict): Updated mapping (settings) dictionary
            importee (str): Absolute path to a file, whose mapping has been updated
        """
        if importee in self.settings.keys():
            self.settings[importee].update(settings)
        else:
            self.settings[importee] = settings

    def _preview_destroyed(self, importee):
        """Destroys preview widget instance for the given importee.

        Args:
            importee (str): Absolute path to a file, whose preview widget is destroyed
        """
        self._preview_widget.pop(importee, None)

    def update_file_model(self, items):
        """Adds given list of items to the file model. If None or
        an empty list is given, the model is cleared.

        Args:
            items (set): Set of absolute file paths
        """
        self.all_files = items
        self.file_model.clear()
        self.file_model.setHorizontalHeaderItem(0, QStandardItem("Source files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setEditable(False)
                qitem.setCheckable(True)
                if item in self.unchecked_files:
                    qitem.setCheckState(Qt.Unchecked)
                else:
                    qitem.setCheckState(Qt.Checked)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.file_model.appendRow(qitem)

    def _run_importer_program(self, args):
        """Starts and runs the importer program in a separate process.

        Args:
            args (list): List of arguments for the importer program
        """
        self.importer_process = QProcess()
        self.importer_process.readyReadStandardOutput.connect(self._log_importer_process_stdout)
        self.importer_process.readyReadStandardError.connect(self._log_importer_process_stderr)
        self.importer_process.finished.connect(self.importer_process.deleteLater)
        program_path = os.path.abspath(importer_program.__file__)
        self.importer_process.start(sys.executable, [program_path])
        self.importer_process.waitForStarted()
        self.importer_process.write(json.dumps(args).encode("utf-8"))
        self.importer_process.write(b'\n')
        self.importer_process.closeWriteChannel()
        if self.importer_process.state() == QProcess.Running:
            loop = QEventLoop()
            self.importer_process.finished.connect(loop.quit)
            loop.exec_()
        return self.importer_process.exitCode()

    @Slot()
    def _log_importer_process_stdout(self):
        output = str(self.importer_process.readAllStandardOutput().data(), "utf-8").strip()
        self._logger.msg.emit(f"<b>{self.name}</b>: {output}")

    @Slot()
    def _log_importer_process_stderr(self):
        output = str(self.importer_process.readAllStandardError().data(), "utf-8").strip()
        self._logger.msg_error.emit(f"<b>{self.name}</b>: {output}")

    def execute_backward(self, resources):
        """See base class."""
        self.resources_from_downstream = resources.copy()
        return True

    def execute_forward(self, resources):
        """See base class."""
        args = [
            [f for f in self.all_files if f not in self.unchecked_files],
            self.settings,
            [r.url for r in self.resources_from_downstream if r.type_ == "database"],
            self.logs_dir,
            self._properties_ui.cancel_on_error_checkBox.isChecked(),
        ]
        exit_code = self._run_importer_program(args)
        return exit_code == 0

    def stop_execution(self):
        """Stops executing this Importer."""
        super().stop_execution()
        if not self.importer_process:
            return
        self.importer_process.kill()

    def _do_handle_dag_changed(self, resources):
        """See base class."""
        file_list = [r.path for r in resources if r.type_ == "file" and not r.metadata.get("future")]
        self._notify_if_duplicate_file_paths(file_list)
        self.update_file_model(set(file_list))
        if not file_list:
            self.add_notification(
                "This Importer does not have any input data. "
                "Connect Data Connections to this Importer to use their data as input."
            )

    def item_dict(self):
        """Returns a dictionary corresponding to this item."""
        d = super().item_dict()
        # Serialize mappings before saving
        d["mappings"] = self.serialize_mappings(self.settings, self._project.project_dir)
        d["cancel_on_error"] = self._properties_ui.cancel_on_error_checkBox.isChecked()
        return d

    def notify_destination(self, source_item):
        """See base class."""
        if source_item.item_type() == "Data Connection":
            self._logger.msg.emit(
                "Link established. You can define mappings on data from "
                f"<b>{source_item.name}</b> using item <b>{self.name}</b>."
            )
        elif source_item.item_type() == "Data Store":
            # Does this type of link do anything?
            self._logger.msg.emit("Link established.")
        else:
            super().notify_destination(source_item)

    @staticmethod
    def default_name_prefix():
        """see base class"""
        return "Importer"

    def tear_down(self):
        """Closes all preview widgets."""
        for widget in self._preview_widget.values():
            widget.close()

    def _notify_if_duplicate_file_paths(self, file_list):
        """Adds a notification if file_list contains duplicate entries."""
        file_counter = Counter(file_list)
        duplicates = list()
        for file_name, count in file_counter.items():
            if count > 1:
                duplicates.append(file_name)
        if duplicates:
            self.add_notification("Duplicate input files from upstream items:<br>{}".format("<br>".join(duplicates)))

    @staticmethod
    def upgrade_from_no_version_to_version_1(item_name, old_item_dict, old_project_dir):
        """Converts mappings to a list, where each element contains two dictionaries,
        the serialized path dictionary and the mapping dictionary for the file in that
        path."""
        new_importer = dict(old_item_dict)
        mappings = new_importer.get("mappings", {})
        list_of_mappings = list()
        paths = list(mappings.keys())
        for path in paths:
            mapping = mappings[path]
            if "source_type" in mapping and mapping["source_type"] == "CSVConnector":
                _fix_csv_connector_settings(mapping)
            new_path = serialize_path(path, old_project_dir)
            if new_path["relative"]:
                new_path["path"] = os.path.join(".spinetoolbox", "items", new_path["path"])
            list_of_mappings.append([new_path, mapping])
        new_importer["mappings"] = list_of_mappings
        return new_importer

    @staticmethod
    def deserialize_mappings(mappings, project_path):
        """Returns mapping settings as dict with absolute paths as keys.

        Args:
            mappings (list): List where each element contains two dictionaries (path dict and mapping dict)
            project_path (str): Path to project directory

        Returns:
            dict: Dictionary with absolute paths as keys and mapping settings as values
        """
        abs_path_mappings = {}
        for source, mapping in mappings:
            abs_path_mappings[deserialize_path(source, project_path)] = mapping
        return abs_path_mappings

    @staticmethod
    def serialize_mappings(mappings, project_path):
        """Returns a list of mappings, where each element contains two dictionaries,
        the 'serialized' path in a dictionary and the mapping dictionary.

        Args:
            mappings (dict): Dictionary with mapping specifications
            project_path (str): Path to project directory

        Returns:
            list: List where each element contains two dictionaries.
        """
        serialized_mappings = list()
        for source, mapping in mappings.items():  # mappings is a dict with absolute paths as keys and mapping as values
            serialized_mappings.append([serialize_path(source, project_path), mapping])
        return serialized_mappings