Skip to content

Commit bcdaf92

Browse files
committed
Moved lambda request and response handling to their own module.
- Moved lambda request and response handling to their own module. - Added capability to use local docker lambdas. - Added utility to get plan name with plan_id from "Kaava" layer. Update lambda_service.py
1 parent 6fb1b96 commit bcdaf92

File tree

4 files changed

+140
-97
lines changed

4 files changed

+140
-97
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from __future__ import annotations
2+
3+
import json
4+
5+
from qgis.PyQt.QtCore import QByteArray, QObject, QUrl, pyqtSignal
6+
from qgis.PyQt.QtNetwork import QNetworkAccessManager, QNetworkProxy, QNetworkReply, QNetworkRequest
7+
from qgis.PyQt.QtWidgets import QMessageBox
8+
9+
from arho_feature_template.utils.misc_utils import get_active_plan_id, get_plan_name, get_settings
10+
11+
12+
class LambdaService(QObject):
13+
jsons_received = pyqtSignal(dict, dict)
14+
15+
def __init__(self):
16+
super().__init__() # Ensure QObject initialization
17+
# Init network manager
18+
self.network_manager = QNetworkAccessManager()
19+
20+
# Get settings
21+
proxy_host, proxy_port, self.lambda_url = get_settings()
22+
23+
if proxy_host and proxy_port:
24+
# Set up SOCKS5 Proxy if values are provided
25+
proxy = QNetworkProxy()
26+
proxy.setType(QNetworkProxy.Socks5Proxy)
27+
proxy.setHostName(proxy_host)
28+
proxy.setPort(int(proxy_port))
29+
self.network_manager.setProxy(proxy)
30+
31+
def send_request(self, action: str, plan_id: str):
32+
"""Sends a request to the lambda function."""
33+
payload = {"action": action, "plan_uuid": plan_id}
34+
payload_bytes = QByteArray(json.dumps(payload).encode("utf-8"))
35+
36+
request = QNetworkRequest(QUrl(self.lambda_url))
37+
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
38+
39+
reply = self.network_manager.post(request, payload_bytes)
40+
41+
# Connect reply signal to handle the response
42+
reply.finished.connect(lambda: self._process_reply(reply))
43+
44+
def _process_reply(self, reply: QNetworkReply):
45+
"""Processes the reply from the lambda and emits signal."""
46+
plan_id = get_active_plan_id()
47+
48+
if reply.error() != QNetworkReply.NoError:
49+
error_string = reply.errorString()
50+
QMessageBox.critical(None, "API Virhe", f"Lambdan kutsu epäonnistui: {error_string}")
51+
reply.deleteLater()
52+
return
53+
54+
try:
55+
response_data = reply.readAll().data().decode("utf-8")
56+
57+
response_json = json.loads(response_data)
58+
59+
# Determine if the proxy is set up.
60+
if hasattr(self, "network_manager") and self.network_manager.proxy().type() == QNetworkProxy.Socks5Proxy:
61+
# If proxy has been set up, retrieve 'details' directly
62+
details = response_json.get("details", {})
63+
else:
64+
# If proxy has not been set up (using local docker lambda), the response includes 'body'.
65+
# In this case we need to retrieve 'details' from 'body' first.
66+
body = response_json.get("body", {})
67+
details = body.get("details", {})
68+
69+
# Extract the plan JSON for the given plan_id
70+
plan_json = details.get(plan_id, {})
71+
if not isinstance(plan_json, dict):
72+
plan_json = {}
73+
74+
outline_json = None
75+
if plan_json:
76+
geographical_area = plan_json.get("geographicalArea")
77+
if geographical_area:
78+
outline_name = get_plan_name(plan_id, language="fin")
79+
outline_json = {
80+
"type": "Feature",
81+
"properties": {"name": outline_name},
82+
"srid": geographical_area.get("srid"),
83+
"geometry": geographical_area.get("geometry"),
84+
}
85+
86+
if outline_json is None:
87+
outline_json = {} # Fallback to empty dictionary if no outline JSON is created
88+
89+
# Emit the signal with the two JSONs
90+
self.jsons_received.emit(plan_json, outline_json)
91+
92+
except json.JSONDecodeError as e:
93+
QMessageBox.critical(None, "JSON Virhe", f"Failed to parse response JSON: {e}")
94+
finally:
95+
reply.deleteLater()
+27-91
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,26 @@
1+
from __future__ import annotations
2+
13
import json
24

35
from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer
4-
from qgis.PyQt.QtCore import QByteArray, QUrl
5-
from qgis.PyQt.QtNetwork import QNetworkAccessManager, QNetworkProxy, QNetworkReply, QNetworkRequest
66
from qgis.PyQt.QtWidgets import QDialog, QMessageBox
77
from qgis.utils import iface
88

9+
from arho_feature_template.core.lambda_service import LambdaService
910
from arho_feature_template.core.update_plan import LandUsePlan, update_selected_plan
1011
from arho_feature_template.gui.load_plan_dialog import LoadPlanDialog
1112
from arho_feature_template.gui.serialize_plan import SerializePlan
1213
from arho_feature_template.utils.db_utils import get_existing_database_connection_names
13-
from arho_feature_template.utils.misc_utils import get_active_plan_id, get_settings, handle_unsaved_changes
14+
from arho_feature_template.utils.misc_utils import get_active_plan_id, get_layer_by_name, handle_unsaved_changes
1415

1516

1617
class PlanManager:
1718
def __init__(self):
18-
# Init network manager
19-
self.network_manager = QNetworkAccessManager()
20-
proxy_host, proxy_port, self.lambda_url = get_settings()
21-
22-
# SOCKS5 Proxy
23-
proxy = QNetworkProxy()
24-
proxy.setType(QNetworkProxy.Socks5Proxy)
25-
proxy.setHostName(proxy_host)
26-
proxy.setPort(int(proxy_port))
27-
self.network_manager.setProxy(proxy)
28-
29-
self.kaava_layer = self.get_layer_by_name("Kaava")
30-
31-
def get_layer_by_name(self, layer_name):
32-
"""Retrieve a layer by name from the project."""
33-
layers = QgsProject.instance().mapLayersByName(layer_name)
34-
if layers:
35-
return layers[0]
36-
iface.messageBar().pushMessage("Error", f"Layer '{layer_name}' not found", level=3)
37-
return None
19+
self.lambda_service = LambdaService()
20+
self.lambda_service.jsons_received.connect(self.save_plan_jsons)
21+
self.json_plan_path = None
22+
self.json_plan_outline_path = None
23+
self.kaava_layer = get_layer_by_name("Kaava")
3824

3925
def add_new_plan(self):
4026
"""Initiate the process to add a new plan to the Kaava layer."""
@@ -127,82 +113,32 @@ def get_plan_json(self):
127113
"""Serializes plan and plan outline to JSON"""
128114
dialog = SerializePlan()
129115
if dialog.exec_() == QDialog.Accepted:
130-
json_plan_path = dialog.plan_path_edit.text()
131-
json_plan_outline_path = dialog.plan_outline_path_edit.text()
116+
self.json_plan_path = dialog.plan_path_edit.text()
117+
self.json_plan_outline_path = dialog.plan_outline_path_edit.text()
132118

133119
plan_id = get_active_plan_id()
134-
135120
if not plan_id:
136-
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa. Luo tai avaa kaava.")
121+
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa.")
137122
return
138123

139-
payload = {"action": "get_plans", "plan_uuid": plan_id}
140-
payload_bytes = QByteArray(json.dumps(payload).encode("utf-8"))
141-
142-
request = QNetworkRequest(QUrl(self.lambda_url))
143-
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
124+
self.lambda_service.send_request("get_plans", plan_id)
144125

145-
reply = self.network_manager.post(request, payload_bytes)
146-
147-
# Connect the finished signal to a lambda to pass extra arguments
148-
reply.finished.connect(lambda: self._handle_response(reply, json_plan_path, json_plan_outline_path))
149-
150-
def _handle_response(self, reply: QNetworkReply, json_plan_path: str, json_plan_outline_path: str):
151-
"""Handle the API response and process the plan and outline data."""
152-
if reply.error() != QNetworkReply.NoError:
153-
error_string = reply.errorString()
154-
QMessageBox.critical(None, "Virhe API kutsussa", f"Virhe: {error_string}")
126+
def save_plan_jsons(self, plan_json, outline_json):
127+
"""This slot saves the plan and outline JSONs to files."""
128+
if plan_json is None or outline_json is None:
129+
QMessageBox.critical(None, "Virhe", "Kaava tai sen ulkoraja ei löytynyt.")
155130
return
156131

157-
try:
158-
response_data = reply.readAll().data().decode("utf-8")
159-
response_json = json.loads(response_data)
160-
161-
details = response_json.get("details")
162-
if not details:
163-
QMessageBox.warning(None, "Varoitus", "Vastauksesta puuttuu 'details' kenttä.")
164-
return
165-
166-
# Extract plan and write it to file
167-
plan_id = get_active_plan_id()
168-
if not plan_id:
169-
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa.")
170-
return
132+
# Retrieve paths
133+
if self.json_plan_path is None or self.json_plan_outline_path is None:
134+
QMessageBox.critical(None, "Virhe", "Tiedostopolut eivät ole saatavilla.")
135+
return
171136

172-
plan_json = details.get(plan_id)
173-
if not plan_json:
174-
QMessageBox.warning(None, "Varoitus", f"Aktiiviselle kaavalle (id: {plan_id}) ei löydy tietoja.")
175-
return
137+
# Save the JSONs
138+
with open(self.json_plan_path, "w", encoding="utf-8") as full_file:
139+
json.dump(plan_json, full_file, ensure_ascii=False, indent=2)
176140

177-
with open(json_plan_path, "w", encoding="utf-8") as full_file:
178-
json.dump(plan_json, full_file, ensure_ascii=False, indent=2)
179-
180-
# Process the geographicalArea for the outline
181-
geographical_area = plan_json.get("geographicalArea")
182-
if geographical_area:
183-
try:
184-
# Build the structured outline JSON and write to file
185-
outline_json = {
186-
"type": "Feature",
187-
"properties": {"name": "Example Polygon"},
188-
"srid": geographical_area.get("srid"),
189-
"geometry": geographical_area.get("geometry"),
190-
}
191-
192-
with open(json_plan_outline_path, "w", encoding="utf-8") as outline_file:
193-
json.dump(outline_json, outline_file, ensure_ascii=False, indent=2)
194-
195-
QMessageBox.information(None, "Success", "Kaava ja sen ulkorja tallennettu onnistuneesti.")
196-
except KeyError as e:
197-
QMessageBox.critical(
198-
None,
199-
"Virhe",
200-
f"'geographicalArea' ei sisällä vaadittuja tietoja: {e}",
201-
)
202-
else:
203-
QMessageBox.warning(None, "Varoitus", "Kenttä 'geographicalArea' puuttuu kaavan tiedoista.")
204-
except json.JSONDecodeError as e:
205-
QMessageBox.critical(None, "Virhe", f"Vastaus JSON:in purkaminen epäonnistui: {e}")
141+
with open(self.json_plan_outline_path, "w", encoding="utf-8") as outline_file:
142+
json.dump(outline_json, outline_file, ensure_ascii=False, indent=2)
206143

207-
# Clean up the reply
208-
reply.deleteLater()
144+
QMessageBox.information(None, "Tallennus onnistui", "Kaava ja sen ulkoraja tallennettu onnistuneesti.")

arho_feature_template/gui/serialize_plan.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99

1010
class SerializePlan(QDialog, FormClass): # type: ignore
11+
plan_outline_path_edit: QLineEdit
12+
plan_path_edit: QLineEdit
13+
plan_outline_select_button: QPushButton
14+
plan_select_button: QPushButton
15+
1116
def __init__(self):
1217
super().__init__()
1318
self.setupUi(self)
1419

15-
self.plan_outline_path_edit = self.findChild(QLineEdit, "plan_outline_path_edit")
16-
self.plan_path_edit = self.findChild(QLineEdit, "plan_path_edit")
17-
self.plan_outline_select_button = self.findChild(QPushButton, "plan_outline_select_button")
18-
self.plan_select_button = self.findChild(QPushButton, "plan_select_button")
19-
2020
self.plan_outline_select_button.clicked.connect(self.select_plan_outline_file)
2121
self.plan_select_button.clicked.connect(self.select_plan_file)
2222

arho_feature_template/utils/misc_utils.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ def handle_unsaved_changes() -> bool:
102102

103103
def get_active_plan_id():
104104
"""Retrieve the active plan ID stored as a project variable."""
105-
# return QgsExpressionContextUtils.projectScope(QgsProject.instance(), "active_plan_id")
106105
return QgsExpressionContextUtils.projectScope(QgsProject.instance()).variable("active_plan_id")
107106

108107

@@ -113,3 +112,16 @@ def get_settings():
113112
proxy_port = settings.value("proxy_port", "5443")
114113
lambda_url = settings.value("lambda_url", "https://t5w26iqnsf.execute-api.eu-central-1.amazonaws.com/v0/ryhti")
115114
return proxy_host, proxy_port, lambda_url
115+
116+
117+
def get_plan_name(plan_id: str, language: Literal["fin", "eng", "swe"] = "fin") -> str:
118+
"""Retrieve the name of a plan from the 'Kaava' layer based on its ID."""
119+
layer = get_layer_by_name("Kaava")
120+
if layer:
121+
for feature in layer.getFeatures():
122+
if feature["id"] == plan_id:
123+
name_field = feature["name"]
124+
name = name_field.get(language, "")
125+
# Return "Nimetön" if the name is an empty string
126+
return name if name.strip() else "Nimetön"
127+
return "Nimetön"

0 commit comments

Comments
 (0)