Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
79e05e7
- updated invalid columnspan to colspan attribute
doublebyte1 Dec 8, 2025
985c155
- added layout and scrollable area to top level widget
doublebyte1 Dec 8, 2025
a12de62
- put back connections
doublebyte1 Dec 8, 2025
a69ed6f
- introduced server radio
doublebyte1 Dec 8, 2025
49b80e5
- added server config dialog
doublebyte1 Dec 8, 2025
fbaf594
- added option to specify server protocol
doublebyte1 Dec 8, 2025
76e795b
- added support for pushing and pulling configiration from a pygeoapi…
doublebyte1 Dec 8, 2025
3bc869a
- added another radio for local file option
doublebyte1 Dec 15, 2025
93ba679
- added server_config_dialog.ui to pb_tool configuration
doublebyte1 Dec 15, 2025
b1cc03f
- added more expressive error messages
doublebyte1 Dec 15, 2025
17f09ad
remove redundant data import functionality; make sure to check diff o…
KatKatKateryna Dec 17, 2025
9bd10af
remove redundant datetime conversion
KatKatKateryna Dec 17, 2025
602cf2a
centralize stringifying datetime strings for Diff and Save
KatKatKateryna Dec 18, 2025
33e379d
accept +0000 format datetime (comes from requests)
KatKatKateryna Dec 19, 2025
e68ee93
ignore datetime diffs for different datetime formats
KatKatKateryna Dec 19, 2025
7c7ffc8
consider case where datetime was parsed from the beginning (was never…
KatKatKateryna Dec 19, 2025
0797389
bring back yaml representer removing string quotes from datetime objects
KatKatKateryna Dec 19, 2025
a5f584a
ensure utf-8 encoding on push_to_server
KatKatKateryna Dec 19, 2025
280df83
typo
KatKatKateryna Dec 19, 2025
375338d
- set test plattform to Windows
doublebyte1 Dec 29, 2025
b70e111
- added unit test for server push and pull
doublebyte1 Dec 29, 2025
5b1ed99
- Added GitHub action to test the server round trip
doublebyte1 Dec 29, 2025
292e445
use default language for Metadata and Resources dict
KatKatKateryna Dec 31, 2025
bec3a7d
confirm before deleting resource
KatKatKateryna Jan 2, 2026
7ea1acc
Merge pull request #9 from byteroad/server-tests
KatKatKateryna Jan 2, 2026
af61c2b
Merge pull request #12 from KatKatKateryna/kate/server_connection_fixes
doublebyte1 Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pygeoapi_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

# Import the code for the dialog
from .pygeoapi_config_dialog import PygeoapiConfigDialog

import os.path


Expand Down
160 changes: 148 additions & 12 deletions pygeoapi_config_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
"""

from copy import deepcopy
from datetime import datetime, timezone
from datetime import date, datetime, timezone
import os
from wsgiref import headers
import requests
import yaml

from .utils.data_diff import diff_yaml_dict

from .ui_widgets.utils import get_url_status

from .server_config_dialog import Ui_serverDialog

from .models.top_level.providers.records import ProviderTypes
from .ui_widgets.providers.NewProviderWindow import NewProviderWindow
Expand Down Expand Up @@ -72,13 +75,50 @@
except:
pass

headers = {
'accept': '*/*',
'Content-Type': 'application/json'
}

def preprocess_for_json(d):
"""Recursively converts datetime/date objects in a dict to ISO strings."""
if isinstance(d, dict):
return {k: preprocess_for_json(v) for k, v in d.items()}
elif isinstance(d, list):
return [preprocess_for_json(i) for i in d]
elif isinstance(d, (datetime, date)):
return d.isoformat()
Comment thread
KatKatKateryna marked this conversation as resolved.
Outdated
return d

class ServerConfigDialog(QDialog, Ui_serverDialog):
"""
Logic for the Server Configuration Dialog.
Inherits from QDialog (functionality) and Ui_serverDialog (layout).
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self) # Builds the UI defined in Designer

# Optional: Set default values based on current config if needed
# self.ServerHostlineEdit.setText("localhost")

def get_server_data(self):
"""
Retrieve the server configuration data entered by the user.
:return: A dictionary with 'host' and 'port' keys.
"""
host = self.ServerHostlineEdit.text()
port = self.ServerSpinBox.value()
protocol = 'http' if self.radioHttp.isChecked() else 'https'
return {'host': host, 'port': port, 'protocol': protocol}


# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
os.path.join(os.path.dirname(__file__), "pygeoapi_config_dialog_base.ui")
)


class PygeoapiConfigDialog(QtWidgets.QDialog, FORM_CLASS):

config_data: ConfigData
Expand Down Expand Up @@ -144,22 +184,118 @@ def on_button_clicked(self, button):
# You can also check the standard button type
if button == self.buttonBox.button(QDialogButtonBox.Save):
if self._set_validate_ui_data()[0]:
file_path, _ = QFileDialog.getSaveFileName(
self, "Save File", "", "YAML Files (*.yml);;All Files (*)"
)

# before saving, show diff with "Procced" and "Cancel" options
if file_path and self._diff_original_and_current_data():
self.save_to_file(file_path)
if self.serverRadio.isChecked():
self.server_config(save=True)
Comment thread
KatKatKateryna marked this conversation as resolved.
Outdated
else:
file_path, _ = QFileDialog.getSaveFileName(
self, "Save File", "", "YAML Files (*.yml);;All Files (*)"
)

# before saving, show diff with "Procced" and "Cancel" options
if file_path and self._diff_original_and_current_data():
self.save_to_file(file_path)

elif button == self.buttonBox.button(QDialogButtonBox.Open):
file_name, _ = QFileDialog.getOpenFileName(
self, "Open File", "", "YAML Files (*.yml);;All Files (*)"
)
self.open_file(file_name)
if self.serverRadio.isChecked():
self.server_config(save=False)
Comment thread
KatKatKateryna marked this conversation as resolved.
Outdated
else:
file_name, _ = QFileDialog.getOpenFileName(
self, "Open File", "", "YAML Files (*.yml);;All Files (*)"
)
self.open_file(file_name)

elif button == self.buttonBox.button(QDialogButtonBox.Close):
self.reject()
return

def server_config(self, save):

dialog = ServerConfigDialog(self)

if dialog.exec_():
data = dialog.get_server_data()
url = f"{data['protocol']}://{data['host']}:{data['port']}/admin/config"
if save == True:
self.push_to_server(url)
else:
self.pull_from_server(url)

def push_to_server(self, url):

QMessageBox.information(
self,
"Information",
f"Pushing configuration to: {url}",
)

config_dict = self.config_data.asdict_enum_safe(self.config_data)

# Pre-process the dictionary to handle datetime objects
processed_config_dict = preprocess_for_json(config_dict)
Comment thread
KatKatKateryna marked this conversation as resolved.
Outdated

# TODO: support authentication through the QT framework
try:
# Send the PUT request to Admin API
response = requests.put(url, headers=headers, json=processed_config_dict)
response.raise_for_status()

QgsMessageLog.logMessage(
f"Success! Status Code: {response.status_code}")

except requests.exceptions.RequestException as e:
QgsMessageLog.logMessage(f"An error occurred: {e}")
Comment thread
doublebyte1 marked this conversation as resolved.


def pull_from_server(self, url):
Comment thread
KatKatKateryna marked this conversation as resolved.

QMessageBox.information(
self,
"Information",
f"Pulling configuration from: {url}",
)

# TODO: support authentication through the QT framework
try:
# Send the GET request to Admin API
response = requests.get(url, headers=headers)
response.raise_for_status()

QgsMessageLog.logMessage(
f"Success! Status Code: {response.status_code}")

QgsMessageLog.logMessage(
f"Response: {response.text}")

data_dict = response.json()

self.config_data = ConfigData()
self.config_data.set_data_from_yaml(data_dict)
self.ui_setter.set_ui_from_data()

# log messages about missing or mistyped values during deserialization
QgsMessageLog.logMessage(
f"Errors during deserialization: {self.config_data.error_message}"
)
QgsMessageLog.logMessage(
f"Default values used for missing YAML fields: {self.config_data.defaults_message}"
)

# summarize all properties missing/overwitten with defaults
# atm, warning with the full list of properties
all_missing_props = self.config_data.all_missing_props
QgsMessageLog.logMessage(
f"All missing or replaced properties: {self.config_data.all_missing_props}"
)
if len(all_missing_props) > 0:
ReadOnlyTextDialog(
self,
"Warning",
f"All missing or replaced properties (check logs for more details): {self.config_data.all_missing_props}",
).exec_()

except requests.exceptions.RequestException as e:
QgsMessageLog.logMessage(f"An error occurred: {e}")

def save_to_file(self, file_path):

Expand Down
Loading