From c7cc9676aaa51a857746502257b136e140291e02 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Tue, 21 Oct 2025 09:56:38 +0200 Subject: [PATCH 1/7] plugins: metadata-importer: remove metadata-importer plugin This will become the metastat-plugin with features specific for the metastat project. --- .../metadata_importer/__init__.py | 167 --- .../metadata_importer/widgets.py | 962 ------------------ eddy/plugins/metadata-importer/plugin.spec | 40 - 3 files changed, 1169 deletions(-) delete mode 100644 eddy/plugins/metadata-importer/metadata_importer/__init__.py delete mode 100644 eddy/plugins/metadata-importer/metadata_importer/widgets.py delete mode 100644 eddy/plugins/metadata-importer/plugin.spec diff --git a/eddy/plugins/metadata-importer/metadata_importer/__init__.py b/eddy/plugins/metadata-importer/metadata_importer/__init__.py deleted file mode 100644 index 4328c9b3..00000000 --- a/eddy/plugins/metadata-importer/metadata_importer/__init__.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- - -########################################################################## -# # -# Eddy: a graphical editor for the specification of Graphol ontologies # -# Copyright (C) 2015 Daniele Pantaleone # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see . # -# # -# ##################### ##################### # -# # -# Graphol is developed by members of the DASI-lab group of the # -# Dipartimento di Ingegneria Informatica, Automatica e Gestionale # -# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it # -# # -# - Domenico Lembo # -# - Valerio Santarelli # -# - Domenico Fabio Savo # -# - Daniele Pantaleone # -# - Marco Console # -# # -########################################################################## - -from PyQt5 import ( - QtCore, - QtGui, -) - -from eddy.core.functions.signals import connect, disconnect -from eddy.core.metadata import K_REPO_MONITOR -from eddy.core.output import getLogger -from eddy.core.plugin import AbstractPlugin -from eddy.ui.dock import DockWidget -from .widgets import MetadataImporterWidget # noqa - -LOGGER = getLogger() - - -class MetadataImporterPlugin(AbstractPlugin): - """ - Search and import ontology metadata from an external service. - """ - sgnProjectChanged = QtCore.pyqtSignal(str) - sgnUpdateState = QtCore.pyqtSignal() - - def __init__(self, spec, session): - """ - Initialises a new instance of the metadata importer plugin. - :type spec: PluginSpec - :type session: Session - """ - super().__init__(spec, session) - - ############################################# - # EVENTS - ################################# - - def eventFilter(self, source: QtCore.QObject, event: QtCore.QEvent) -> bool: - """ - Filters events if this object has been installed as an event filter for the watched object. - """ - if event.type() == QtCore.QEvent.Resize: - widget = source.widget() - widget.redraw() - return super().eventFilter(source, event) - - ############################################# - # SLOTS - ################################# - - @QtCore.pyqtSlot() - def onSessionReady(self): - """ - Executed whenever the main session completes the startup sequence. - """ - widget = self.widget('metadata_importer') # type: MetadataImporterWidget - # CONNECT TO PROJECT SPECIFIC SIGNALS - self.debug('Connecting to project: %s', self.project.name) - connect(self.project.sgnPrefixAdded, widget.onPrefixChanged) - connect(self.project.sgnPrefixModified, widget.onPrefixChanged) - connect(self.project.sgnPrefixRemoved, widget.onPrefixChanged) - connect(K_REPO_MONITOR.sgnUpdated, widget.doUpdateState) - widget.onPrefixChanged() - widget.doUpdateState() - - @QtCore.pyqtSlot() - def doUpdateState(self): - """ - Executed when the plugin session updates its state. - """ - pass - - ############################################# - # HOOKS - ################################# - - def dispose(self): - """ - Executed whenever the plugin is going to be destroyed. - """ - # DISCONNECT FROM CURRENT PROJECT - widget = self.widget('metadata_importer') # type: MetadataImporterWidget - self.debug('Disconnecting from project: %s', self.project.name) - disconnect(self.project.sgnPrefixAdded, widget.onPrefixChanged) - disconnect(self.project.sgnPrefixModified, widget.onPrefixChanged) - disconnect(self.project.sgnPrefixRemoved, widget.onPrefixChanged) - disconnect(K_REPO_MONITOR.sgnUpdated, widget.doUpdateState) - - # DISCONNECT FROM ACTIVE SESSION - self.debug('Disconnecting from active session') - disconnect(self.session.sgnReady, self.onSessionReady) - disconnect(self.session.sgnUpdateState, self.doUpdateState) - - def start(self): - """ - Perform initialization tasks for the plugin. - """ - # INITIALIZE THE WIDGET - self.debug('Starting Metadata Importer plugin') - widget = MetadataImporterWidget(self) - widget.setObjectName('metadata_importer') - self.addWidget(widget) - - # CREATE DOCKING AREA WIDGET - self.debug('Creating docking area widget') - widget = DockWidget('Metadata Importer', QtGui.QIcon(':icons/18/ic_explore_black'), self.session) - widget.installEventFilter(self) - widget.setAllowedAreas( - QtCore.Qt.LeftDockWidgetArea - | QtCore.Qt.RightDockWidgetArea - | QtCore.Qt.BottomDockWidgetArea - ) - widget.setObjectName('metadata_importer_dock') - widget.setWidget(self.widget('metadata_importer')) - self.addWidget(widget) - - # CREATE SHORTCUTS - action = widget.toggleViewAction() - action.setParent(self.session) - action.setShortcut(QtGui.QKeySequence('Alt+8')) - - # CREATE ENTRY IN VIEW MENU - self.debug('Creating docking area widget toggle in "view" menu') - menu = self.session.menu('view') - menu.addAction(self.widget('metadata_importer_dock').toggleViewAction()) - - # INSTALL DOCKING AREA WIDGET - self.debug('Installing docking area widget') - self.session.addDockWidget( - QtCore.Qt.LeftDockWidgetArea, - self.widget('metadata_importer_dock'), - ) - - # CONFIGURE SIGNAL/SLOTS - connect(self.session.sgnReady, self.onSessionReady) - connect(self.session.sgnUpdateState, self.doUpdateState) diff --git a/eddy/plugins/metadata-importer/metadata_importer/widgets.py b/eddy/plugins/metadata-importer/metadata_importer/widgets.py deleted file mode 100644 index 67842f85..00000000 --- a/eddy/plugins/metadata-importer/metadata_importer/widgets.py +++ /dev/null @@ -1,962 +0,0 @@ -# -*- coding: utf-8 -*- - -########################################################################## -# # -# Eddy: a graphical editor for the specification of Graphol ontologies # -# Copyright (C) 2015 Daniele Pantaleone # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see . # -# # -# ##################### ##################### # -# # -# Graphol is developed by members of the DASI-lab group of the # -# Dipartimento di Ingegneria Informatica, Automatica e Gestionale # -# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it # -# # -# - Domenico Lembo # -# - Valerio Santarelli # -# - Domenico Fabio Savo # -# - Daniele Pantaleone # -# - Marco Console # -# # -########################################################################## - -from abc import ( - ABCMeta, - abstractmethod, -) -import json -import textwrap -from typing import ( - Any, - cast, -) - -from PyQt5 import ( - QtCore, - QtGui, - QtNetwork, - QtWidgets, -) -from rdflib import Graph -from rdflib.namespace import NamespaceManager - -from eddy.core.commands.iri import CommandIRIAddAnnotationAssertion -from eddy.core.datatypes.graphol import Item -from eddy.core.functions.misc import first -from eddy.core.functions.signals import connect -from eddy.core.metadata import ( - Entity, - K_GRAPH, - LiteralValue, - MetadataRequest, - NamedEntity, - Repository, -) -from eddy.core.output import getLogger -from eddy.ui.fields import ( - ComboBox, - IntegerField, - StringField, - TextField, -) - -LOGGER = getLogger() - - -class MetadataImporterWidget(QtWidgets.QWidget): - """ - This class implements the metadata importer used to search external metadata sources. - """ - sgnItemActivated = QtCore.pyqtSignal(QtGui.QStandardItem) - sgnItemClicked = QtCore.pyqtSignal(QtGui.QStandardItem) - sgnItemDoubleClicked = QtCore.pyqtSignal(QtGui.QStandardItem) - sgnItemRightClicked = QtCore.pyqtSignal(QtGui.QStandardItem) - - def __init__(self, plugin): - """ - Initialize the metadata importer widget. - :type plugin: Session - """ - super().__init__(plugin.session) - - self.plugin = plugin - self.settings = QtCore.QSettings() - self.iconAttribute = QtGui.QIcon(':/icons/18/ic_treeview_attribute') - self.iconConcept = QtGui.QIcon(':/icons/18/ic_treeview_concept') - self.iconInstance = QtGui.QIcon(':/icons/18/ic_treeview_instance') - self.iconRole = QtGui.QIcon(':/icons/18/ic_treeview_role') - self.iconValue = QtGui.QIcon(':/icons/18/ic_treeview_value') - - self.search = StringField(self) - self.search.setAcceptDrops(False) - self.search.setClearButtonEnabled(True) - self.search.setPlaceholderText('Search...') - self.search.setFixedHeight(30) - self.combobox = QtWidgets.QComboBox(self) - self.combobox.addItems(map(lambda r: r.name, Repository.load())) - self.combobox.setCurrentIndex(self.settings.value('metadata/index', 0, int)) - self.model = QtGui.QStandardItemModel(self) - self.proxy = MetadataImporterFilterProxyModel(self) - self.proxy.setDynamicSortFilter(False) - self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.proxy.setSortCaseSensitivity(QtCore.Qt.CaseSensitive) - self.proxy.setSourceModel(self.model) - self.entityview = MetadataImporterView(self) - self.entityview.setModel(self.proxy) - self.details = MetadataInfoWidget(self) - - self.searchLayout = QtWidgets.QHBoxLayout() - self.searchLayout.setContentsMargins(0, 0, 0, 0) - self.searchLayout.addWidget(self.search) - self.searchLayout.addWidget(self.combobox) - self.mainLayout = QtWidgets.QVBoxLayout(self) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.addLayout(self.searchLayout) - self.mainLayout.addWidget(self.entityview) - self.mainLayout.addWidget(self.details) - self.setTabOrder(self.search, self.combobox) - self.setTabOrder(self.combobox, self.entityview) - self.setTabOrder(self.entityview, self.details) - - self.setContentsMargins(0, 0, 0, 0) - self.setMinimumWidth(216) - self.setStyleSheet(""" - QLineEdit, - QLineEdit:editable, - QLineEdit:hover, - QLineEdit:pressed, - QLineEdit:focus { - border: none; - border-radius: 0; - background: #FFFFFF; - color: #000000; - padding: 4px 4px 4px 4px; - } - """) - - connect(self.entityview.activated, self.onItemActivated) - connect(self.entityview.doubleClicked, self.onItemDoubleClicked) - connect(self.entityview.pressed, self.onItemPressed) - connect(self.search.textChanged, self.doFilterItem) - connect(self.search.returnPressed, self.onReturnPressed) - connect(self.combobox.currentIndexChanged, self.onRepositoryChanged) - # connect(self.sgnItemActivated, self.session.doFocusItem) - # connect(self.sgnItemDoubleClicked, self.session.doFocusItem) - # connect(self.sgnItemRightClicked, self.session.doFocusItem) - - ############################################# - # PROPERTIES - ################################# - - @property - def project(self): - """ - Returns the reference to the active project. - :rtype: Session - """ - return self.session.project - - @property - def session(self): - """ - Returns the reference to the active session. - :rtype: Session - """ - return self.plugin.parent() - - ############################################# - # EVENTS - ################################# - - def paintEvent(self, paintEvent): - """ - This is needed for the widget to pick the stylesheet. - :type paintEvent: QPaintEvent - """ - option = QtWidgets.QStyleOption() - option.initFrom(self) - painter = QtGui.QPainter(self) - style = self.style() - style.drawPrimitive(QtWidgets.QStyle.PE_Widget, option, painter, self) - - ############################################# - # SLOTS - ################################# - - @QtCore.pyqtSlot(str) - def doFilterItem(self, key): - """ - Executed when the search box is filled with data. - :type key: str - """ - self.proxy.setFilterFixedString(key) - self.proxy.sort(QtCore.Qt.AscendingOrder) - - @QtCore.pyqtSlot() - def doUpdateState(self): - """ - Executed to refresh the metadata widget. - """ - repos = Repository.load() - if len(repos) > 0: - index = self.combobox.currentIndex() - # Prevent signals while refreshing the combobox - status = self.combobox.blockSignals(True) - self.combobox.clear() - self.combobox.addItems(map(lambda r: r.name, repos)) - self.combobox.blockSignals(status) - # Force repetition of current selection to trigger refresh - self.combobox.setCurrentIndex(index) - self.combobox.currentIndexChanged.emit(index) - else: - self.model.clear() - self.details.repository = None - self.details.entity = None - self.details.stack() - - @QtCore.pyqtSlot(QtCore.QModelIndex) - def onItemActivated(self, index): - """ - Executed when an item in the list view is activated (e.g. by pressing Return or Enter key). - :type index: QModelIndex - """ - # noinspection PyArgumentList - if QtWidgets.QApplication.mouseButtons() == QtCore.Qt.NoButton: - item = self.model.itemFromIndex(self.proxy.mapToSource(index)) - if item: - self.details.entity = item.data() - self.sgnItemActivated.emit(item) - # KEEP FOCUS ON THE TREE VIEW UNLESS SHIFT IS PRESSED - if QtWidgets.QApplication.queryKeyboardModifiers() & QtCore.Qt.SHIFT: - return - self.entityview.setFocus() - self.details.stack() - - @QtCore.pyqtSlot(QtCore.QModelIndex) - def onItemDoubleClicked(self, index): - """ - Executed when an item in the list view is double-clicked. - :type index: QModelIndex - """ - # noinspection PyArgumentList - if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.LeftButton: - item = self.model.itemFromIndex(self.proxy.mapToSource(index)) - if item: - self.details.entity = item.data() - self.details.repository = item.data().repository - self.sgnItemDoubleClicked.emit(item) - self.details.stack() - - @QtCore.pyqtSlot(QtCore.QModelIndex) - def onItemPressed(self, index): - """ - Executed when an item in the treeview is clicked. - :type index: QModelIndex - """ - # noinspection PyArgumentList - if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.LeftButton: - item = self.model.itemFromIndex(self.proxy.mapToSource(index)) - if item: - self.details.entity = item.data() - #self.details.repository = item.data().repository - self.sgnItemDoubleClicked.emit(item) - self.details.stack() - - @QtCore.pyqtSlot() - @QtCore.pyqtSlot(str) - @QtCore.pyqtSlot(str, str) - def onPrefixChanged(self, _name: str = None, _ns: str = None): - """ - Executed when a project prefix is changed to update the medatata namespace manager. - """ - # There is currently no support for unbinding a namespace in rdflib, - # so we have to resort to recreating it from scratch. - # See: https://github.com/RDFLib/rdflib/issues/1932 - K_GRAPH.namespace_manager = NamespaceManager(Graph(), bind_namespaces='none') - for prefix, ns in self.project.prefixDictItems(): - K_GRAPH.bind(prefix, ns, override=True) - self.redraw() - - @QtCore.pyqtSlot() - def onReturnPressed(self): - """ - Executed when the Return or Enter key is pressed in the search field. - """ - self.focusNextChild() - - @QtCore.pyqtSlot(int) - def onRepositoryChanged(self, index): - """ - Executed when the selected repository in the combobox changes. - """ - name = self.combobox.itemText(index) - repo = first(Repository.load(), filter_on_item=lambda i: i.name == name) - self.model.clear() - if repo: - self.settings.setValue('metadata/index', self.combobox.currentIndex()) - url = QtCore.QUrl(repo.uri) - url.setPath(f'{url.path()}/classes') - request = QtNetwork.QNetworkRequest(url) - request.setAttribute(MetadataRequest.RepositoryAttribute, repo) - reply = self.session.nmanager.get(request) - connect(reply.finished, self.onRequestCompleted) - else: - repo = None - self.details.repository = repo - self.details.entity = None - self.details.stack() - - @QtCore.pyqtSlot() - def onRequestCompleted(self): - """ - Executed when a metadata request has completed to update the widget. - """ - reply = self.sender() - try: - reply.deleteLater() - if reply.isFinished() and reply.error() == QtNetwork.QNetworkReply.NoError: - data = json.loads(str(reply.readAll(), encoding='utf-8')) - entities = [NamedEntity.from_dict(d) for d in data if "iri" in d] - for e in entities: - e.repository = reply.request().attribute(MetadataRequest.RepositoryAttribute) - try: - itemText = K_GRAPH.namespace_manager.curie(e.iri, generate=False) - except KeyError: - itemText = e.iri - item = QtGui.QStandardItem(self.iconConcept, f"{itemText}") - item.setData(e) - self.model.appendRow(item) - self.session.statusBar().showMessage('Metadata fetch completed') - elif reply.isFinished() and reply.error() != QtNetwork.QNetworkReply.NoError: - msg = f'Failed to retrieve metadata: {reply.errorString()}' - LOGGER.warning(msg) - self.session.statusBar().showMessage(msg) - except Exception as e: - LOGGER.error(f'Failed to retrieve metadata: {e}') - - ############################################# - # INTERFACE - ################################# - - def redraw(self) -> None: - """ - Redraw the content of the widget. - """ - for index in range(self.model.rowCount()): - item = self.model.item(index, 0) - if isinstance(item.data(), NamedEntity): - try: - itemText = K_GRAPH.namespace_manager.curie(item.data().iri, generate=False) - except KeyError: - itemText = item.data().iri - item.setText(itemText) - self.entityview.update() - self.details.redraw() - - def sizeHint(self): - """ - Returns the recommended size for this widget. - :rtype: QtCore.QSize - """ - return QtCore.QSize(216, 266) - - -class MetadataImporterView(QtWidgets.QListView): - """ - This class implements the metadata importer list view. - """ - def __init__(self, parent): - """ - Initialize the metadata importer list view. - :type parent: MetadataImporterWidget - """ - super().__init__(parent) - self.startPos = None - self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) - self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setHorizontalScrollMode(QtWidgets.QTreeView.ScrollPerPixel) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self.setSelectionMode(QtWidgets.QListView.SelectionMode.SingleSelection) - self.setWordWrap(True) - # self.setItemDelegate(MetadataImporterItemDelegate(self)) - - ############################################# - # PROPERTIES - ################################# - - @property - def session(self): - """ - Returns the reference to the Session holding the MetadataImporter widget. - :rtype: Session - """ - return self.widget.session - - @property - def widget(self): - """ - Returns the reference to the MetadataImporter widget. - :rtype: MetadataImporterWidget - """ - return self.parent() - - ############################################# - # EVENTS - ################################# - - def mousePressEvent(self, mouseEvent): - """ - Executed when the mouse is pressed on the treeview. - :type mouseEvent: QMouseEvent - """ - self.clearSelection() - if mouseEvent.buttons() & QtCore.Qt.LeftButton: - self.startPos = mouseEvent.pos() - super().mousePressEvent(mouseEvent) - - def mouseMoveEvent(self, mouseEvent): - """ - Executed when the mouse if moved while a button is being pressed. - :type mouseEvent: QMouseEvent - """ - if mouseEvent.buttons() & QtCore.Qt.LeftButton: - distance = (mouseEvent.pos() - self.startPos).manhattanLength() - if distance >= QtWidgets.QApplication.startDragDistance(): - index = first(self.selectedIndexes()) - if index: - model = self.model().sourceModel() - index = self.model().mapToSource(index) - item = model.itemFromIndex(index) - data = item.data() - if data: - if isinstance(data, Entity): - mimeData = QtCore.QMimeData() - mimeData.setText(str(Item.ConceptNode.value)) - buf = QtCore.QByteArray() - buf.append(data.name) - mimeData.setData(str(Item.ConceptNode.value), buf) - drag = QtGui.QDrag(self) - drag.setMimeData(mimeData) - drag.exec_(QtCore.Qt.CopyAction) - - # Add assertion indicating source - from eddy.core.owl import IRI, AnnotationAssertion - subj = self.session.project.getIRI(str(data.name)) # type: IRI - pred = self.session.project.getIRI('urn:x-graphol:origin') - loc = QtCore.QUrl(data.repository.uri) - loc.setPath(f'{loc.path()}/entities/{data.id}'.replace('//', '/')) - obj = IRI(loc.toString()) - ast = AnnotationAssertion(subj, pred, obj) - cmd = CommandIRIAddAnnotationAssertion(self.session.project, subj, ast) - self.session.undostack.push(cmd) - - super().mouseMoveEvent(mouseEvent) - - def paintEvent(self, event: QtGui.QPaintEvent): - """ - Overrides paintEvent to display a placeholder text. - """ - super().paintEvent(event) - if self.model().rowCount() == 0: - painter = QtGui.QPainter(self.viewport()) - painter.save() - painter.setPen(self.palette().placeholderText().color()) - fm = self.fontMetrics() - bgMsg = 'No Metadata Available' - elided_text = fm.elidedText(bgMsg, QtCore.Qt.ElideRight, self.viewport().width()) - painter.drawText(self.viewport().rect(), QtCore.Qt.AlignCenter, elided_text) - painter.restore() - - ############################################# - # INTERFACE - ################################# - - def sizeHintForColumn(self, column): - """ - Returns the size hint for the given column. - This will make the column of the treeview as wide as the widget that contains the view. - :type column: int - :rtype: int - """ - return max(super().sizeHintForColumn(column), self.viewport().width()) - - -class MetadataImporterFilterProxyModel(QtCore.QSortFilterProxyModel): - """ - Extends QSortFilterProxyModel adding filtering functionalities for the metadata importer - """ - def __init__(self, parent=None): - super().__init__(parent) - - ############################################# - # INTERFACE - ################################# - - def filterAcceptsRow(self, sourceRow: int, sourceParent: QtCore.QModelIndex) -> bool: - """ - Overrides filterAcceptsRow to include extra filtering conditions - :type sourceRow: int - :type sourceParent: QModelIndex - :rtype: bool - """ - return sourceParent.isValid() or super().filterAcceptsRow(sourceRow, sourceParent) - - -class MetadataInfoWidget(QtWidgets.QScrollArea): - """ - This class implements the metadata detail widget. - """ - def __init__(self, parent: QtWidgets.QWidget) -> None: - """ - Initialize the metadata info box. - """ - super().__init__(parent) - - self.repository = None - self.entity = None - self.stacked = QtWidgets.QStackedWidget(self) - self.stacked.setContentsMargins(0, 0, 0, 0) - self.infoEmpty = EmptyInfo(self.stacked) - self.infoRepository = RepositoryInfo(self.stacked) - self.infoEntity = EntityInfo(self.stacked) - self.stacked.addWidget(self.infoEmpty) - self.stacked.addWidget(self.infoRepository) - self.stacked.addWidget(self.infoEntity) - self.setContentsMargins(0, 0, 0, 0) - self.setMinimumSize(QtCore.QSize(216, 120)) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self.setWidget(self.stacked) - self.setWidgetResizable(True) - - self.setStyleSheet(""" - MetadataInfoWidget { - background: #FFFFFF; - } - MetadataInfoWidget Header { - background: #5A5050; - padding-left: 4px; - color: #FFFFFF; - } - MetadataInfoWidget Key { - background: #BBDEFB; - border-top: none; - border-right: none; - border-bottom: 1px solid #BBDEFB; - border-left: none; - padding: 0 0 0 4px; - } - MetadataInfoWidget Button, - MetadataInfoWidget Button:focus, - MetadataInfoWidget Button:hover, - MetadataInfoWidget Button:hover:focus, - MetadataInfoWidget Button:pressed, - MetadataInfoWidget Button:pressed:focus, - MetadataInfoWidget Text, - MetadataInfoWidget Integer, - MetadataInfoWidget String, - MetadataInfoWidget Select, - MetadataInfoWidget Parent { - background: #E3F2FD; - border-top: none; - border-right: none; - border-bottom: 1px solid #BBDEFB !important; - border-left: 1px solid #BBDEFB !important; - padding: 0 0 0 4px; - text-align:left; - } - MetadataInfoWidget Button::menu-indicator { - image: none; - } - MetadataInfoWidget Select:!editable, - MetadataInfoWidget Select::drop-down:editable { - background: #FFFFFF; - } - MetadataInfoWidget Select:!editable:on, - MetadataInfoWidget Select::drop-down:editable:on { - background: #FFFFFF; - } - MetadataInfoWidget QCheckBox { - background: #FFFFFF; - spacing: 0; - margin-left: 4px; - margin-top: 2px; - } - MetadataInfoWidget QCheckBox::indicator:disabled { - background-color: #BABABA; - } - """) - - scrollbar = self.verticalScrollBar() - scrollbar.installEventFilter(self) - - ############################################# - # EVENTS - ################################# - - def eventFilter(self, source: QtCore.QObject, event: QtCore.QEvent) -> bool: - """ - Filter incoming events. - """ - if source is self.verticalScrollBar(): - if event.type() in {QtCore.QEvent.Show, QtCore.QEvent.Hide}: - self.redraw() - return super().eventFilter(source, event) - - ############################################# - # INTERFACE - ################################# - - def redraw(self) -> None: - """ - Redraw the content of the widget. - """ - width = self.width() - scrollbar = self.verticalScrollBar() - if scrollbar.isVisible(): - width -= scrollbar.width() - widget = self.stacked.currentWidget() - widget.setFixedWidth(width) - sizeHint = widget.sizeHint() - height = sizeHint.height() - self.stacked.setFixedWidth(width) - # self.stacked.setFixedHeight(clamp(height, 0)) - - def stack(self) -> None: - """ - Set the current stacked widget. - """ - if self.entity: - show = self.infoEntity - show.updateData(self.repository, self.entity) - elif self.repository: - show = self.infoRepository - show.updateData(self.repository) - else: - show = self.infoEmpty - - prev = self.stacked.currentWidget() - self.stacked.setCurrentWidget(show) - self.redraw() - if prev is not show: - scrollbar = self.verticalScrollBar() - scrollbar.setValue(0) - - -############################################# -# COMPONENTS -################################# - - -class Header(QtWidgets.QLabel): - """ - This class implements the header of properties section. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the header. - """ - super().__init__(*args) - self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) - self.setFixedHeight(24) - - -class Key(QtWidgets.QLabel): - """ - This class implements the key of an info field. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the key. - """ - super().__init__(*args) - self.setFixedSize(88, 20) - - -class Button(QtWidgets.QPushButton): - """ - This class implements the button to which associate a QtWidgets.QMenu instance of an info field. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the button. - """ - super().__init__(*args) - - -class Integer(IntegerField): - """ - This class implements the integer value of an info field. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the field. - """ - super().__init__(*args) - self.setFixedHeight(20) - - -class String(StringField): - """ - This class implements the string value of an info field. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the field. - """ - super().__init__(*args) - self.setFixedHeight(20) - self.setReadOnly(True) - - -class Text(TextField): - """ - This class implements the string value of an info field. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the field. - """ - super().__init__(*args) - self.setFixedHeight(20 * (self.document().lineCount() + 1)) - self.setReadOnly(True) - - -class Select(ComboBox): - """ - This class implements the selection box of an info field. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the field. - """ - super().__init__(*args) - self.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setScrollEnabled(False) - - -class Parent(QtWidgets.QWidget): - """ - This class implements the parent placeholder to be used - to store checkbox and radio button value fields. - """ - def __init__(self, *args: Any) -> None: - """ - Initialize the field. - """ - super().__init__(*args) - self.setFixedHeight(20) - - def paintEvent(self, paintEvent: QtGui.QPaintEvent) -> None: - """ - This is needed for the widget to pick the stylesheet. - """ - option = QtWidgets.QStyleOption() - option.initFrom(self) - painter = QtGui.QPainter(self) - style = self.style() - style.drawPrimitive(QtWidgets.QStyle.PE_Widget, option, painter, self) - - -############################################# -# INFO WIDGETS -################################# - -class AbstractInfo(QtWidgets.QWidget): - """ - This class implements the base information box. - """ - __metaclass__ = ABCMeta - - def __init__(self, parent: QtWidgets.QWidget = None) -> None: - """ - Initialize the base information box. - """ - super().__init__(parent) - self.setContentsMargins(0, 0, 0, 0) - - ############################################# - # INTERFACE - ################################# - - @abstractmethod - def updateData(self, **kwargs: Any) -> None: - """ - Fetch new information and fill the widget with data. - """ - pass - - -class RepositoryInfo(AbstractInfo): - """ - This class implements the repository information box. - """ - def __init__(self, parent: QtWidgets.QWidget = None) -> None: - """ - Initialize the repository information box. - """ - super().__init__(parent) - - self.nameKey = Key('Name', self) - self.nameField = String(self) - self.nameField.setReadOnly(True) - - self.uriKey = Key('Location', self) - self.uriField = String(self) - self.uriField.setReadOnly(True) - - self.versionKey = Key('Version', self) - self.versionField = String(self) - self.versionField.setReadOnly(True) - - self.infoHeader = Header('Repository Info', self) - self.infoLayout = QtWidgets.QFormLayout() - self.infoLayout.setSpacing(0) - self.infoLayout.addRow(self.nameKey, self.nameField) - self.infoLayout.addRow(self.uriKey, self.uriField) - self.infoLayout.addRow(self.versionKey, self.versionField) - - self.mainLayout = QtWidgets.QVBoxLayout(self) - self.mainLayout.setAlignment(QtCore.Qt.AlignTop) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(0) - self.mainLayout.insertWidget(0, self.infoHeader) - self.mainLayout.insertLayout(1, self.infoLayout) - - ############################################# - # INTERFACE - ################################# - - def updateData(self, repository: Repository) -> None: - """ - Fetch new information and fill the widget with data. - """ - self.nameField.setValue(repository.name) - self.uriField.setValue(repository.uri) - self.versionField.setValue('1.0') - - -class EntityInfo(AbstractInfo): - """ - This class implements the information box for entities. - """ - def __init__(self, parent: QtWidgets.QWidget = None) -> None: - """ - Initialize the generic node information box. - """ - super().__init__(parent) - - self.entity = None - self.repoKey = Key('Repository', self) - self.repoField = String(self) - self.repoField.setReadOnly(True) - - self.idKey = Key('Entity ID', self) - self.idField = String(self) - self.idField.setReadOnly(True) - - self.iriKey = Key('Entity IRI', self) - self.iriField = String(self) - self.iriField.setReadOnly(True) - - self.nodePropHeader = Header('Entity properties', self) - self.nodePropLayout = QtWidgets.QFormLayout() - self.nodePropLayout.setSpacing(0) - self.nodePropLayout.addRow(self.repoKey, self.repoField) - self.nodePropLayout.addRow(self.idKey, self.idField) - self.nodePropLayout.addRow(self.iriKey, self.iriField) - - self.typesHeader = Header('Entity Types', self) - self.typesLayout = QtWidgets.QFormLayout() - self.typesLayout.setSpacing(0) - - self.metadataHeader = Header('Entity Annotations', self) - self.metadataLayout = QtWidgets.QFormLayout() - self.metadataLayout.setSpacing(0) - - self.mainLayout = QtWidgets.QVBoxLayout(self) - self.mainLayout.setAlignment(QtCore.Qt.AlignTop) - self.mainLayout.setContentsMargins(0, 0, 0, 0) - self.mainLayout.setSpacing(0) - self.mainLayout.addWidget(self.nodePropHeader) - self.mainLayout.addLayout(self.nodePropLayout) - self.mainLayout.addWidget(self.typesHeader) - self.mainLayout.addLayout(self.typesLayout) - self.mainLayout.addWidget(self.metadataHeader) - self.mainLayout.addLayout(self.metadataLayout) - - ############################################# - # INTERFACE - ################################# - - def updateData(self, repository: Repository, entity: Entity) -> None: - """ - Fetch new information and fill the widget with data. - """ - self.repoField.setValue(repository.uri) - self.idField.setValue(entity.id) - self.iriField.setValue(entity.name) - - # ENTITY TYPES - while self.typesLayout.rowCount() > 0: - self.typesLayout.removeRow(0) - for t in entity.types: - self.typesLayout.addRow(Key('Type', self), String(t.name, self)) - - # ENTITY ANNOTATIONS - while self.metadataLayout.rowCount() > 0: - self.metadataLayout.removeRow(0) - for a in entity.annotations: - self.metadataLayout.addRow(Key('Property', self), String(a.predicate.n3(), self)) - if isinstance(a.object, LiteralValue): - literal = cast(LiteralValue, a.object) - value, lang, dtype = literal.value, literal.language, literal.datatype - self.metadataLayout.addRow(Key('Value', self), Text(value, self)) - if lang: - self.metadataLayout.addRow(Key('lang', self), String(lang, self)) - if dtype: - self.metadataLayout.addRow(Key('dtype', self), String(dtype.n3(), self)) - else: - self.metadataLayout.addRow(Key('Entity', self), String(a.object.n3(), self)) - self.metadataLayout.addItem(QtWidgets.QSpacerItem(10, 2)) - - -class EmptyInfo(QtWidgets.QTextEdit): - """ - This class implements the information box when there is no metadata repository. - """ - - ############################################# - # INTERFACE - ################################# - - def paintEvent(self, event: QtGui.QPaintEvent): - """ - Overrides paintEvent to display a placeholder text. - """ - super().paintEvent(event) - painter = QtGui.QPainter(self.viewport()) - painter.save() - painter.setPen(self.palette().placeholderText().color()) - fm = self.fontMetrics() - bgMsg = textwrap.dedent(""" - To start using the 'Metadata Importer', add a repository location - in the 'Ontology Manager -> Metadata Repositories' tab. - """) - elided_text = fm.elidedText(bgMsg, QtCore.Qt.ElideRight, self.viewport().width()) - painter.drawText(self.viewport().rect(), QtCore.Qt.AlignCenter, elided_text) - painter.restore() diff --git a/eddy/plugins/metadata-importer/plugin.spec b/eddy/plugins/metadata-importer/plugin.spec deleted file mode 100644 index 5889ebc6..00000000 --- a/eddy/plugins/metadata-importer/plugin.spec +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- - -########################################################################## -# # -# Eddy: a graphical editor for the specification of Graphol ontologies # -# Copyright (C) 2015 Daniele Pantaleone # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see . # -# # -# ##################### ##################### # -# # -# Graphol is developed by members of the DASI-lab group of the # -# Dipartimento di Ingegneria Informatica, Automatica e Gestionale # -# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it # -# # -# - Domenico Lembo # -# - Valerio Santarelli # -# - Domenico Fabio Savo # -# - Daniele Pantaleone # -# - Marco Console # -# # -########################################################################## - -[plugin] -author: OBDA Systems -contact: info@obdasystems.com -id: metadata_importer -name: Metadata Importer -version: 0.2.0 From d81023c5c6d380a0cca28ba3c50c3050e34035c5 Mon Sep 17 00:00:00 2001 From: MariaRosaria <45686128+MariaRosariaFraraccio@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:06:37 +0100 Subject: [PATCH 2/7] metastat: freeze annotations for entities coming from metastat --- eddy/ui/iri.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/eddy/ui/iri.py b/eddy/ui/iri.py index d8340a34..1b14945e 100644 --- a/eddy/ui/iri.py +++ b/eddy/ui/iri.py @@ -58,7 +58,7 @@ ) from eddy.core.common import HasWidgetSystem from eddy.core.diagram import Diagram -from eddy.core.functions.signals import connect +from eddy.core.functions.signals import connect, disconnect from eddy.core.items.nodes.attribute import AttributeNode from eddy.core.items.nodes.common.base import AbstractNode from eddy.core.items.nodes.facet import FacetNode @@ -643,13 +643,14 @@ def accept(self): if self.iri: if not str(self.iri) == inputIriString: if len(self.project.iriOccurrences(iri=self.iri)) == 1: - existIRI = self.project.existIRI(inputIriString) - if existIRI: - newIRI = self.project.getIRI(inputIriString, - addLabelFromSimpleName=True, - addLabelFromUserInput=True, - userInput=userExplicitInput, - labelLang=labelLang) + if self.project.existIRI(inputIriString): + newIRI = self.project.getIRI( + inputIriString, + addLabelFromSimpleName=True, + addLabelFromUserInput=True, + userInput=userExplicitInput, + labelLang=labelLang, + ) if newIRI is not self.iri: oldIRI = self.iri self.iri = newIRI @@ -797,6 +798,15 @@ def __init__(self, iri, session, focusOnAnnotations=False): connect(addBtn.clicked, self.addAnnotation) connect(delBtn.clicked, self.removeAnnotation) connect(editBtn.clicked, self.editAnnotation) + ############################## + # Disable edit - add - delete if entity has an external origin annotation + if any([str(a.assertionProperty) == 'urn:x-graphol:origin' + for a in self.iri.annotationAssertions]): + addBtn.setDisabled(True) + delBtn.setDisabled(True) + editBtn.setDisabled(True) + disconnect(table.cellDoubleClicked, self.editAnnotation) + ############################## self.addWidget(addBtn) self.addWidget(delBtn) self.addWidget(editBtn) From daceb014bdb25cdb332c914b268889ae6b58afcb Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Tue, 2 Dec 2025 14:22:18 +0100 Subject: [PATCH 3/7] core: add signal notifying item drop on diagram Can be used to override the item drop action from plugins. Anything that overrides the handling can set the drop action to Ignore to stop the builtin handler as the accepted property of the event is not reliable. --- eddy/core/diagram.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/eddy/core/diagram.py b/eddy/core/diagram.py index 83974ea7..da8c1282 100644 --- a/eddy/core/diagram.py +++ b/eddy/core/diagram.py @@ -96,6 +96,7 @@ class Diagram(QtWidgets.QGraphicsScene): Extension of QtWidgets.QGraphicsScene which implements a single Graphol diagram. Additionally to built-in signals, this class emits: + * sgnDragDropEvent: whenever data is dropped onto the Diagram. * sgnItemAdded: whenever an element is added to the Diagram. * sgnItemInsertionCompleted: whenever an item 'MANUAL' insertion process is completed. * sgnItemRemoved: whenever an element is removed from the Diagram. @@ -110,6 +111,7 @@ class Diagram(QtWidgets.QGraphicsScene): MaxFontSize = 40 SelectionRadius = 4 + sgnDragDropEvent = QtCore.pyqtSignal(QtWidgets.QGraphicsScene, QtWidgets.QGraphicsSceneDragDropEvent) sgnItemAdded = QtCore.pyqtSignal(QtWidgets.QGraphicsScene, QtWidgets.QGraphicsItem) sgnItemInsertionCompleted = QtCore.pyqtSignal(QtWidgets.QGraphicsItem, int) sgnItemRemoved = QtCore.pyqtSignal(QtWidgets.QGraphicsScene, QtWidgets.QGraphicsItem) @@ -235,15 +237,25 @@ def dropEvent(self, dropEvent: QtWidgets.QGraphicsSceneDragDropEvent) -> None: Executed when a dragged element is dropped on the diagram. """ super().dropEvent(dropEvent) - if dropEvent.mimeData().hasFormat('text/plain') and Item.valueOf(dropEvent.mimeData().text()): + self.sgnDragDropEvent.emit(self, dropEvent) + if ( + dropEvent.dropAction() == QtCore.Qt.DropAction.CopyAction + and dropEvent.mimeData().hasFormat('text/plain') + and Item.valueOf(dropEvent.mimeData().text()) + ): snapToGrid = self.session.action('toggle_grid').isChecked() node = self.factory.create(Item.valueOf(dropEvent.mimeData().text())) node.setPos(snap(dropEvent.scenePos(), Diagram.GridSize, snapToGrid)) data = dropEvent.mimeData().data(dropEvent.mimeData().text()) - if int(dropEvent.mimeData().text()) in {Item.ConceptNode, Item.AttributeNode, - Item.RoleNode, Item.IndividualNode, - Item.ValueDomainNode, Item.LiteralNode, - Item.FacetNode}: + if Item.valueOf(dropEvent.mimeData().text()) in { + Item.ConceptNode, + Item.AttributeNode, + Item.RoleNode, + Item.IndividualNode, + Item.ValueDomainNode, + Item.LiteralNode, + Item.FacetNode, + }: if not data: # For new nodes (e.g. drag and drop from palette) if isinstance(node, FacetNode): From 698f4d4dbda48478d884a0d80d0149fd0b56eee8 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Fri, 5 Dec 2025 11:55:49 +0100 Subject: [PATCH 4/7] Revert "ui: ontology: add tab for metadata repositories" This reverts commit 3cdb58ae2c8388d7ad3a13b11a0ded7e766d57f7. --- eddy/ui/ontology.py | 161 -------------------------------------------- 1 file changed, 161 deletions(-) diff --git a/eddy/ui/ontology.py b/eddy/ui/ontology.py index 12807831..831915e3 100644 --- a/eddy/ui/ontology.py +++ b/eddy/ui/ontology.py @@ -32,8 +32,6 @@ # # ########################################################################## -import textwrap - from PyQt5 import ( QtCore, QtGui, @@ -79,7 +77,6 @@ from eddy.core.functions.misc import first from eddy.core.functions.path import expandPath from eddy.core.functions.signals import connect -from eddy.core.metadata import Repository from eddy.core.ndc import ( ADMS, NDCDataset, @@ -1152,67 +1149,6 @@ def __init__(self, session): self.setDistributionSuggestions() self.setProjectSuggestions() - ############################################# - # METADATA REPOSITORIES - ################################# - - table = QtWidgets.QTableWidget(0, 2, self, objectName='repository_table_widget') - table.setHorizontalHeaderLabels(['Name', 'Endpoint']) - table.horizontalHeader().setStretchLastSection(True) - table.horizontalHeader().setSectionsClickable(False) - table.horizontalHeader().setMinimumSectionSize(100) - table.horizontalHeader().setSectionsClickable(False) - table.verticalHeader().setVisible(False) - table.verticalHeader().setSectionsClickable(False) - table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self.addWidget(table) - - delBtn = QtWidgets.QPushButton('Remove', objectName='repository_del_button') - delBtn.setEnabled(False) - connect(delBtn.clicked, self.removeRepository) - self.addWidget(delBtn) - - boxlayout = QtWidgets.QHBoxLayout() - boxlayout.setAlignment(QtCore.Qt.AlignCenter) - boxlayout.addWidget(delBtn) - formlayout = QtWidgets.QFormLayout() - formlayout.addRow(self.widget('repository_table_widget')) - formlayout.addRow(boxlayout) - - groupbox = QtWidgets.QGroupBox('Repositories', self) - groupbox.setObjectName('repository_list_groupbox') - groupbox.setLayout(formlayout) - self.addWidget(groupbox) - - nameField = StringField(self, objectName='repository_name_field') - uriField = StringField(self, objectName='repository_uri_field') - addBtn = QtWidgets.QPushButton('Add', objectName='repository_add_button') - connect(addBtn.clicked, self.addRepository) - self.addWidget(nameField) - self.addWidget(uriField) - self.addWidget(addBtn) - - boxlayout = QtWidgets.QHBoxLayout() - boxlayout.setAlignment(QtCore.Qt.AlignCenter) - boxlayout.addWidget(addBtn) - formlayout = QtWidgets.QFormLayout() - formlayout.addRow(QtWidgets.QLabel('Name'), nameField) - formlayout.addRow(QtWidgets.QLabel('URI'), uriField) - formlayout.addRow(boxlayout) - - groupbox = QtWidgets.QGroupBox('Add Repository', self) - groupbox.setObjectName('repository_add_groupbox') - groupbox.setLayout(formlayout) - self.addWidget(groupbox) - - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.widget('repository_list_groupbox')) - layout.addWidget(self.widget('repository_add_groupbox')) - widget = QtWidgets.QWidget() - widget.setObjectName('repositories_widget') - widget.setLayout(layout) - self.addWidget(widget) - ############################################# # CONFIRMATION BOX ################################# @@ -1235,7 +1171,6 @@ def __init__(self, session): widget.addTab(self.widget('annotations_widget'), 'Annotations') widget.addTab(self.widget('iri_widget'), 'Global IRIs') widget.addTab(self.widget('assertions_widget'), 'Annotation Assertions') - widget.addTab(self.widget('repositories_widget'), 'Metadata Repositories') widget.addTab(self.widget('NDCmetadata_widget'), 'NDC Metadata') self.addWidget(widget) @@ -1439,25 +1374,6 @@ def redraw(self): preField = self.widget('pre_input_field') postField = self.widget('post_input_field') - ############################################# - # METADATA REPOSITORIES - ################################# - - widget = self.widget('repository_table_widget') # type: QtWidgets.QTableWidget - widget.clearContents() - repos = Repository.load() - widget.setRowCount(len(repos)) - for index, repo in enumerate(repos): - nameItem = QtWidgets.QTableWidgetItem(repo.name) - nameItem.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) - uriItem = QtWidgets.QTableWidgetItem(repo.uri) - uriItem.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) - widget.setItem(index, 0, nameItem) - widget.setItem(index, 1, uriItem) - widget.resizeColumnsToContents() - widget.sortItems(0) - self.widget('repository_del_button').setEnabled(len(repos) > 0) - ############################################# # GENERAL TAB ################################# @@ -2709,80 +2625,3 @@ def doAddMetadata(self): self.session.undostack.endMacro() # self.redraw() self.session.addNotification('Metadata added to the current project!') - - ############################################# - # METADATA REPOSITORIES - ################################# - - @QtCore.pyqtSlot() - def addRepository(self): - """Shows a dialog to insert a add a new repository.""" - nameField = self.widget('repository_name_field') # type: StringField - uriField = self.widget('repository_uri_field') # type: StringField - - # Validate user input - if len(nameField.text()) == 0: - msgBox = QtWidgets.QMessageBox( # noqa - QtWidgets.QMessageBox.Warning, - 'Invalid Repository Name', - 'Please specify a repository name.', - informativeText=textwrap.dedent(""" - The repository name can be any string that is used to easily - reference the repository. - """), - parent=self, - ) - msgBox.open() - elif not QtCore.QUrl(uriField.text()).isValid(): - msgBox = QtWidgets.QMessageBox( # noqa - QtWidgets.QMessageBox.Warning, - 'Invalid Repository URI', - 'Please specify a valid repository URI.', - informativeText=textwrap.dedent(""" - The repository URI is the base path at which the repository API is accessible, - and must include protocol, domain and port (if any). - - e.g.: - https://example.com:5000/ - https://example.com/myrepo/ - """), - parent=self, - ) - msgBox.open() - else: - # Add new repository - repos = Repository.load() - if any(map(lambda r: r.name == nameField.text(), repos)): - msgBox = QtWidgets.QMessageBox( # noqa - QtWidgets.QMessageBox.Warning, - 'Duplicate Repository Error', - f'A repository named {nameField.text()} already exists.', - informativeText=textwrap.dedent(""" - Repository names must be unique to avoid abiguity in the user interface. - """), - parent=self, - ) - msgBox.open() - else: - repos.append(Repository(name=nameField.text(), uri=uriField.text())) - Repository.save(repos) - self.redraw() - - @QtCore.pyqtSlot() - def removeRepository(self): - """Remove selected repositories.""" - # Delete selected repositories - widget = self.widget('repository_table_widget') # type: QtWidgets.QTableWidget - selections = widget.selectedRanges() - for sel in selections: - for row in range(sel.bottomRow(), sel.topRow() + 1): - widget.removeRow(row) - # Save the current repositories list - repos = [] - for row in range(widget.rowCount()): - repos.append(Repository( - name=widget.item(row, 0).text(), - uri=widget.item(row, 1).text(), - )) - Repository.save(repos) - self.redraw() From 042853af396f714f58d310593fd6cbcc0b1a4153 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Fri, 5 Dec 2025 11:57:07 +0100 Subject: [PATCH 5/7] Revert "core: metadata: add monitor of repository changes" This reverts commit 353c64dc5919145b1a7d712aaa934de77f773c97. --- eddy/core/metadata.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/eddy/core/metadata.py b/eddy/core/metadata.py index d8059221..f7240996 100644 --- a/eddy/core/metadata.py +++ b/eddy/core/metadata.py @@ -63,25 +63,6 @@ K_GRAPH = Graph(bind_namespaces='none') -class RepositoryMonitor(QtCore.QObject): - """ - This class can be used to listen for changes in the saved repository list. - """ - sgnUpdated = QtCore.pyqtSignal() - _instance = None - - def __new__(cls): - """ - Implements the singleton pattern. - """ - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - -K_REPO_MONITOR = RepositoryMonitor() - - @unique class MetadataRequest(IntEnum_): """ @@ -138,7 +119,6 @@ def save(cls, repositories: List[Repository]) -> None: settings.setValue('name', repo.name) settings.setValue('uri', repo.uri) settings.endArray() - K_REPO_MONITOR.sgnUpdated.emit() class Node(metaclass=ABCMeta): From cfa19cfa56eca784c998127055e8460340a26e81 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Fri, 5 Dec 2025 11:57:33 +0100 Subject: [PATCH 6/7] Revert "core: metadata: add metadata api classes" This reverts commit dc0177f7e8d2f9ad4db9d1943e47cd27c5bd5fcf. --- eddy/core/metadata.py | 415 ------------------------------------------ 1 file changed, 415 deletions(-) delete mode 100644 eddy/core/metadata.py diff --git a/eddy/core/metadata.py b/eddy/core/metadata.py deleted file mode 100644 index f7240996..00000000 --- a/eddy/core/metadata.py +++ /dev/null @@ -1,415 +0,0 @@ -# -*- coding: utf-8 -*- - -########################################################################## -# # -# Eddy: a graphical editor for the specification of Graphol ontologies # -# Copyright (C) 2015 Daniele Pantaleone # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see . # -# # -# ##################### ##################### # -# # -# Graphol is developed by members of the DASI-lab group of the # -# Dipartimento di Ingegneria Informatica, Automatica e Gestionale # -# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it # -# # -# - Domenico Lembo # -# - Valerio Santarelli # -# - Domenico Fabio Savo # -# - Daniele Pantaleone # -# - Marco Console # -# # -########################################################################## - -from __future__ import annotations - -from abc import ( - ABCMeta, - abstractmethod, -) -from enum import unique -from typing import ( - List, - Optional, - cast, -) - -from PyQt5 import ( - QtCore, - QtNetwork, -) -from rdflib import ( - BNode, - Graph, - IdentifiedNode, - Literal, - URIRef, -) - -from eddy.core.datatypes.common import IntEnum_ - -# Graph instance used to keep track of namespaces -K_GRAPH = Graph(bind_namespaces='none') - - -@unique -class MetadataRequest(IntEnum_): - """ - This class defines the attributes used in metadata REST requests. - """ - RepositoryAttribute = QtNetwork.QNetworkRequest.Attribute(7001) - - -class Repository: - """ - A repository of metadata as a RESTful API endpoint. - """ - - def __init__(self, name: str, uri: URIRef | str): - """Initialize the repository instance.""" - self._name = name - self._uri = uri - # TODO: Authentication mechanims will be added later - - @property - def name(self): - """Returns the repository name.""" - return self._name - - @property - def uri(self): - """Returns the repository uri.""" - return self._uri - - ############################################# - # INTERFACE - ################################# - - @classmethod - def load(cls) -> List[Repository]: - """Load the repositories list from user preferences.""" - repos = [] # type: List[Repository] - settings = QtCore.QSettings() - for index in range(settings.beginReadArray('metadata/repositories')): - settings.setArrayIndex(index) - repos.append(Repository( - name=settings.value('name'), - uri=settings.value('uri'), - )) - return repos - - @classmethod - def save(cls, repositories: List[Repository]) -> None: - """Save the repository in the user preferences.""" - settings = QtCore.QSettings() - settings.beginWriteArray('metadata/repositories') - for index, repo in enumerate(repositories): - settings.setArrayIndex(index) - settings.setValue('name', repo.name) - settings.setValue('uri', repo.uri) - settings.endArray() - - -class Node(metaclass=ABCMeta): - """ - Base class for any API object. - """ - - @classmethod - @abstractmethod - def from_dict(cls, data: dict, **kwargs: dict) -> Node: - """Creates a new node from the given dict.""" - pass - - @abstractmethod - def to_dict(self, deep: bool = False) -> dict: - """Serializes the object to a dict.""" - pass - - @abstractmethod - def n3(self) -> str: - """Returns the N-TRIPLES (n3) representation of the object.""" - pass - - -class Entity(Node, metaclass=ABCMeta): - """ - Base class for any named or unnamed entity. - """ - - def __init__(self, id_: str) -> None: - """Initialize the entity.""" - super().__init__() - self._id = id_ - self._types = [] - self._annotations = [] - - @property - def id(self) -> str: - """Return the identifier for this entity.""" - return self._id - - @property - @abstractmethod - def name(self) -> IdentifiedNode: - """Return the IRI or BNode for this entity.""" - pass - - @property - def types(self) -> List[NamedEntity]: - """Return the list of types of this entity.""" - return self._types - - @property - def annotations(self) -> List[Annotation]: - """Return the list of annotations of this entity.""" - return self._annotations - - def __eq__(self, other: Entity) -> bool: - return self.__class__ == other.__class__ and self.id == other.id - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.id})" - - -class NamedEntity(Entity): - """ - Represents entities that are uniquely identified with an IRI. - """ - - def __init__(self, id_: str, iri: URIRef | str) -> None: - """Initialize the named entity.""" - super().__init__(id_) - self._iri = iri if isinstance(iri, URIRef) else URIRef(iri) - - @property - def iri(self) -> URIRef: - """Return the iri associated with this named entity.""" - return self._iri - - @property - def name(self) -> IdentifiedNode: - return self.iri - - @classmethod - def from_dict(cls, data: dict, **kwargs) -> Node: - ent = NamedEntity(data["id"], data["iri"]) # noqa - if "types" in data: - for t in data["types"]: - ent.types.append(cast(NamedEntity, NamedEntity.from_dict(t))) - if "annotations" in data: - for a in data["annotations"]: - ant = cast(Annotation, Annotation.from_dict(a, subject=ent.to_dict())) - ent.annotations.append(ant) - return ent - - def to_dict(self, deep: bool = False) -> dict: - res = { - "id": self.id, - "iri": self.name, - } - if deep: - res |= { - "types": [t.to_dict() for t in self.types], - "annotations": [a.to_dict() for a in self.annotations], - } - return res - - def n3(self) -> str: - return self.iri.n3(K_GRAPH.namespace_manager) - - def __eq__(self, other: Entity) -> bool: - return super().__eq__(other) and self.iri == cast(NamedEntity, other).iri - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.id}, \"{self.iri}\")" - - -class AnonymousEntity(Entity): - """ - Represents entities not identified by an IRI (i.e. blank nodes in RDF). - """ - - def __init__(self, id_: str, bnode: BNode | str) -> None: - """Initialize the anonymous entity.""" - super().__init__(id_) - self._bnode = bnode if isinstance(bnode, BNode) else BNode(bnode) - - @property - def bnode(self) -> BNode: - """Returns the blank node associated with this entity.""" - return self._bnode - - @property - def name(self) -> IdentifiedNode: - return self.bnode - - @classmethod - def from_dict(cls, data: dict, **kwargs) -> Node: - ent = NamedEntity(data["id"], data["bnode"]) # noqa - if "types" in data: - for t in data["types"]: - ent.types.append(cast(NamedEntity, NamedEntity.from_dict(t))) - if "annotations" in data: - for a in data["annotations"]: - ent.annotations.append( - cast(Annotation, Annotation.from_dict(a, subject=ent.to_dict()))) - return ent - - def to_dict(self, deep: bool = False) -> dict: - res = { - "id": self.id, - "bnode": self.name, - } - if deep: - res |= { - "types": [t.to_dict() for t in self.types], - "annotations": [a.to_dict() for a in self.annotations], - } - return res - - def n3(self) -> str: - return self.bnode.n3(K_GRAPH.namespace_manager) - - def __eq__(self, other: Entity) -> bool: - return super().__eq__(other) and self.bnode == cast(AnonymousEntity, other).bnode - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.id}, \"{self.bnode}\")" - - -class LiteralValue(Node): - """ - Represent a literal value associated with an annotation assertion. - """ - - def __init__(self, value: str, language: Optional[str] = None, - datatype: Optional[URIRef | str] = None) -> None: - """Initialize the literal.""" - super().__init__() - self._value = value - self._language = language - self._datatype = datatype - # Check that literal is well-formed - if self._language is not None and self._datatype is not None: - raise ValueError("Literals with a datatype cannot have a language tag.") - - @property - def value(self) -> str: - """Return the value of this literal.""" - return self._value - - @property - def language(self) -> Optional[str]: - """Return the language tag of this literal, or `None` if there is no tag.""" - return self._language - - @property - def datatype(self) -> Optional[URIRef | str]: - """Return the datatype of this literal, or `None` if there is no datatype.""" - return self._datatype - - @classmethod - def from_dict(cls, data: dict, **kwargs) -> Node: - return cls(data["value"], data["language"], data["datatype"]) # noqa - - def to_dict(self, deep: bool = False) -> dict: - return { - "value": self.value, - "language": self.language, - "datatype": self.datatype - } - - def n3(self) -> str: - return Literal(self.value, self.language, self.datatype).n3(K_GRAPH.namespace_manager) - - def __eq__(self, other: Entity) -> bool: - return ( - super().__eq__(other) - and self.value == cast(LiteralValue, other).value - and self.language == cast(LiteralValue, other).language - and self.datatype == cast(LiteralValue, other).datatype - ) - - def __repr__(self) -> str: - if self.language: - return f"{self.__class__.__name__}(\"{self.value}\"@{self.language})" - else: - return f"{self.__class__.__name__}(\"{self.value}\"^^{self.datatype})" - - -class Annotation(Node): - """ - Represent an annotation assertion. - """ - - def __init__(self, subject: Entity, predicate: NamedEntity, object: Node) -> None: - """Initialize the annotation.""" - self._subject = subject - self._predicate = predicate - self._object = object - - @property - def subject(self) -> Entity: - """Return the annotation subject.""" - return self._subject - - @property - def predicate(self) -> NamedEntity: - """Return the annotation predicate.""" - return self._predicate - - @property - def object(self) -> Node: - """Return the annotation object.""" - return self._object - - @classmethod - def from_dict(cls, data: dict, **kwargs) -> Node: - s = kwargs["subject"] - p = data["property"] - v = data["value"] - - sub = NamedEntity.from_dict(s) if "iri" in s else AnonymousEntity.from_dict(s) - prop = NamedEntity.from_dict(p) - - if "id" in v: - obj = NamedEntity.from_dict(v) if "iri" in v else AnonymousEntity.from_dict(v) - else: - obj = LiteralValue.from_dict(v) - - return Annotation(cast(Entity, sub), cast(NamedEntity, prop), obj) - - def to_dict(self, deep: bool = False) -> dict: - return { - "property": self.predicate.to_dict(), - "value": self.object.to_dict() - } - - def n3(self) -> str: - sub = self.subject.n3() - pred = self.predicate.n3() - obj = self.object.n3() - return f'{sub} {pred} {obj}' - - def __eq__(self, other: Annotation) -> bool: - return ( - self.__class__ == other.__class__ - and self.subject == other.subject - and self.predicate == other.predicate - and self.object == other.object - ) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.subject}, {self.predicate}, {self.object})" From db3a1f0929a22c84470dcce9acdc7ca52d80c593 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Fri, 5 Dec 2025 14:29:03 +0100 Subject: [PATCH 7/7] core: exporters: owl2: drop changes for metadata importer plugin --- eddy/core/exporters/owl2.py | 57 ++----------------------------------- 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/eddy/core/exporters/owl2.py b/eddy/core/exporters/owl2.py index 836dfe96..2542a898 100644 --- a/eddy/core/exporters/owl2.py +++ b/eddy/core/exporters/owl2.py @@ -89,10 +89,6 @@ ) from eddy.core.functions.signals import connect from eddy.core.jvm import getJavaVM -from eddy.core.metadata import ( - LiteralValue, - NamedEntity, -) from eddy.core.ndc import ( ADMS, NDCDataset, @@ -481,17 +477,6 @@ def onErrored(self, exception): self.reject() - @QtCore.pyqtSlot(str) - def onMetadataFetchErrored(self, message): - """ - Executed when a metadata fetch request fails. - :type message: str - """ - self.session.addNotification(textwrap.dedent(f""" - ERROR:\n - {message} - """)) - @QtCore.pyqtSlot(str) def onNDCMetadataMissing(self, uri): """ @@ -570,7 +555,6 @@ def run(self): connect(worker.sgnStarted, self.onStarted) connect(worker.sgnCompleted, self.onCompleted) connect(worker.sgnErrored, self.onErrored) - connect(worker.sgnMetadataFetchErrored, self.onMetadataFetchErrored) connect(worker.sgnNDCMetadataMissing, self.onNDCMetadataMissing) connect(worker.sgnProgress, self.onProgress) self.startThread('OWL2Export', worker) @@ -582,7 +566,6 @@ class OWLOntologyExporterWorker(AbstractWorker): """ sgnCompleted = QtCore.pyqtSignal() sgnErrored = QtCore.pyqtSignal(Exception) - sgnMetadataFetchErrored = QtCore.pyqtSignal(str) sgnNDCMetadataMissing = QtCore.pyqtSignal(str) sgnProgress = QtCore.pyqtSignal(int, int) sgnStarted = QtCore.pyqtSignal() @@ -638,7 +621,6 @@ def __init__(self, project, path=None, **kwargs): self._axioms = set() self._converted = dict() self._converted_meta_individuals = dict() - self.metadataProperty = self.project.getIRI('urn:x-graphol:origin') self.df = None self.man = None @@ -1405,43 +1387,8 @@ def createAnnotationAssertionAxioms(self, node): if OWLAxiom.Annotation in self.axiomsList: for annotation in node.iri.annotationAssertions: subject = self.IRI.create(str(annotation.subject)) - if annotation.assertionProperty == self.metadataProperty: - uri = annotation.value - # FIXME: this is a sync request!!! - result, response = self.nmanager.getSync(str(uri)) - if not result: - msg = f'Retrieval of {uri} failed: {response}' - LOGGER.warning(msg) - self.sgnMetadataFetchErrored.emit(msg) - continue - try: - for assertion in NamedEntity.from_dict(json.loads(response)).annotations: - if isinstance(assertion.object, LiteralValue): - from eddy.core.owl import Annotation - value = Annotation( - self.project.getIRI(str(assertion.predicate.iri)), - assertion.object.value, - type=assertion.object.datatype, - language=assertion.object.language, - parent=self.project, - ) - elif isinstance(assertion.object, NamedEntity): - value = Annotation( - self.project.getIRI(str(assertion.predicate.iri)), - self.project.getIRI(str(assertion.object.iri)), - parent=self.project, - ) - else: - LOGGER.warning(f'Skipping annotation with bnode object {assertion}') - value = self.getOWLApiAnnotation(value) - self.addAxiom(self.df.getOWLAnnotationAssertionAxiom(subject, value)) - except Exception as e: - msg = f'Failed to parse metadata for {uri}' - LOGGER.warning(f'{msg}: {response}') - self.sgnMetadataFetchErrored.emit(f'{msg}: See log for details.') - else: - value = self.getOWLApiAnnotation(annotation) - self.addAxiom(self.df.getOWLAnnotationAssertionAxiom(subject, value)) + value = self.getOWLApiAnnotation(annotation) + self.addAxiom(self.df.getOWLAnnotationAssertionAxiom(subject, value)) def createClassAssertionAxiom(self, edge): """