Skip to content

Commit 7bc6a77

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.
1 parent 5340abd commit 7bc6a77

File tree

4 files changed

+138
-88
lines changed

4 files changed

+138
-88
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()

arho_feature_template/core/plan_manager.py

+25-82
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,29 @@
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
1314
from arho_feature_template.utils.misc_utils import (
1415
get_active_plan_id,
1516
get_layer_by_name,
16-
get_settings,
1717
handle_unsaved_changes,
1818
)
1919

2020

2121
class PlanManager:
2222
def __init__(self):
23-
# Init network manager
24-
self.network_manager = QNetworkAccessManager()
25-
proxy_host, proxy_port, self.lambda_url = get_settings()
26-
27-
# SOCKS5 Proxy
28-
proxy = QNetworkProxy()
29-
proxy.setType(QNetworkProxy.Socks5Proxy)
30-
proxy.setHostName(proxy_host)
31-
proxy.setPort(int(proxy_port))
32-
self.network_manager.setProxy(proxy)
33-
23+
self.lambda_service = LambdaService()
24+
self.lambda_service.jsons_received.connect(self.save_plan_jsons)
25+
self.json_plan_path = None
26+
self.json_plan_outline_path = None
3427
self.kaava_layer = get_layer_by_name("Kaava")
3528

3629
def add_new_plan(self):
@@ -124,82 +117,32 @@ def get_plan_json(self):
124117
"""Serializes plan and plan outline to JSON"""
125118
dialog = SerializePlan()
126119
if dialog.exec_() == QDialog.Accepted:
127-
json_plan_path = dialog.plan_path_edit.text()
128-
json_plan_outline_path = dialog.plan_outline_path_edit.text()
120+
self.json_plan_path = dialog.plan_path_edit.text()
121+
self.json_plan_outline_path = dialog.plan_outline_path_edit.text()
129122

130123
plan_id = get_active_plan_id()
131-
132124
if not plan_id:
133-
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa. Luo tai avaa kaava.")
125+
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa.")
134126
return
135127

136-
payload = {"action": "get_plans", "plan_uuid": plan_id}
137-
payload_bytes = QByteArray(json.dumps(payload).encode("utf-8"))
128+
self.lambda_service.send_request("get_plans", plan_id)
138129

139-
request = QNetworkRequest(QUrl(self.lambda_url))
140-
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
141-
142-
reply = self.network_manager.post(request, payload_bytes)
143-
144-
# Connect the finished signal to a lambda to pass extra arguments
145-
reply.finished.connect(lambda: self._handle_response(reply, json_plan_path, json_plan_outline_path))
146-
147-
def _handle_response(self, reply: QNetworkReply, json_plan_path: str, json_plan_outline_path: str):
148-
"""Handle the API response and process the plan and outline data."""
149-
if reply.error() != QNetworkReply.NoError:
150-
error_string = reply.errorString()
151-
QMessageBox.critical(None, "Virhe API kutsussa", f"Virhe: {error_string}")
130+
def save_plan_jsons(self, plan_json, outline_json):
131+
"""This slot saves the plan and outline JSONs to files."""
132+
if plan_json is None or outline_json is None:
133+
QMessageBox.critical(None, "Virhe", "Kaava tai sen ulkoraja ei löytynyt.")
152134
return
153135

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

169-
plan_json = details.get(plan_id)
170-
if not plan_json:
171-
QMessageBox.warning(None, "Varoitus", f"Aktiiviselle kaavalle (id: {plan_id}) ei löydy tietoja.")
172-
return
141+
# Save the JSONs
142+
with open(self.json_plan_path, "w", encoding="utf-8") as full_file:
143+
json.dump(plan_json, full_file, ensure_ascii=False, indent=2)
173144

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

204-
# Clean up the reply
205-
reply.deleteLater()
148+
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)