Skip to content

Commit ff3f468

Browse files
committed
optimize feature requests
1 parent 01fa4f2 commit ff3f468

File tree

4 files changed

+69
-22
lines changed

4 files changed

+69
-22
lines changed

arho_feature_template/core/plan_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def edit_plan(self):
161161
return
162162

163163
active_plan_id = QgsExpressionContextUtils.projectScope(QgsProject.instance()).variable("active_plan_id")
164-
feature = PlanLayer.get_feature_by_id(active_plan_id)
164+
feature = PlanLayer.get_feature_by_attribute_value("id", active_plan_id, no_geometries=False)
165165
if feature is None:
166166
iface.messageBar().pushWarning("", "No active/open plan found!")
167167
return

arho_feature_template/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ def __init__(self, layer_name: str):
2121
super().__init__(f"Layer {layer_name} is not a vector layer")
2222

2323

24+
class LayerNameNotFoundError(Exception):
25+
def __init__(self, layer_name: str):
26+
super().__init__(f"Layer {layer_name} not found")
27+
28+
2429
class ConfigSyntaxError(Exception):
2530
def __init__(self, message: str):
2631
super().__init__(f"Invalid config syntax: {message}")

arho_feature_template/project/layers/__init__.py

+33-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from abc import ABC
4-
from typing import TYPE_CHECKING, ClassVar
4+
from typing import TYPE_CHECKING, Any, ClassVar, Generator
55

66
from qgis.core import QgsFeatureRequest
77

@@ -23,6 +23,35 @@ def get_features(cls) -> QgsFeatureIterator:
2323
return cls.get_from_project().getFeatures()
2424

2525
@classmethod
26-
def get_feature_by_id(cls, _id: str) -> QgsFeature | None:
27-
request = QgsFeatureRequest().setFilterExpression(f"\"id\"='{_id}'")
28-
return next(cls.get_from_project().getFeatures(request), None)
26+
def get_features_by_attribute_value(
27+
cls,
28+
attribute: str,
29+
value: str,
30+
no_geometries: bool = True, # noqa: FBT001, FBT002
31+
) -> Generator[QgsFeature]:
32+
layer = cls.get_from_project()
33+
request = QgsFeatureRequest().setFilterExpression(f"\"{attribute}\"='{value}'")
34+
if no_geometries:
35+
request.setFlags(QgsFeatureRequest.NoGeometry)
36+
yield from layer.getFeatures(request)
37+
38+
@classmethod
39+
def get_feature_by_attribute_value(
40+
cls,
41+
attribute: str,
42+
value: str,
43+
no_geometries: bool = True, # noqa: FBT001, FBT002
44+
) -> QgsFeature | None:
45+
gen = cls.get_features_by_attribute_value(attribute, value, no_geometries)
46+
return next(gen, None)
47+
48+
@classmethod
49+
def get_attribute_values_by_another_attribute_value(
50+
cls, target_attribute: str, filter_attribute: str, filter_value: str
51+
) -> Generator[Any]:
52+
layer = cls.get_from_project()
53+
request = QgsFeatureRequest().setFilterExpression(f"\"{filter_attribute}\"='{filter_value}'")
54+
request.setSubsetOfAttributes([target_attribute], layer.fields())
55+
request.setFlags(QgsFeatureRequest.NoGeometry)
56+
for feature in layer.getFeatures(request):
57+
yield feature[target_attribute]

arho_feature_template/project/layers/plan_layers.py

+30-17
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from numbers import Number
66
from string import Template
77
from textwrap import dedent
8-
from typing import Any, ClassVar
8+
from typing import Any, ClassVar, Generator
99

1010
from qgis.core import NULL, QgsExpressionContextUtils, QgsFeature, QgsProject, QgsVectorLayerUtils
1111
from qgis.utils import iface
1212

1313
from arho_feature_template.core.models import Plan, PlanFeature, Regulation, RegulationGroup, RegulationLibrary
14-
from arho_feature_template.exceptions import FeatureNotFoundError, LayerEditableError
14+
from arho_feature_template.exceptions import FeatureNotFoundError, LayerEditableError, LayerNotFoundError
1515
from arho_feature_template.project.layers import AbstractLayer
1616
from arho_feature_template.project.layers.code_layers import PlanRegulationTypeLayer
1717

@@ -48,7 +48,7 @@ def feature_from_model(cls, model: Any) -> QgsFeature:
4848
@classmethod
4949
def initialize_feature_from_model(cls, model: Any) -> QgsFeature:
5050
if model.id_ is not None: # Expects all plan layer models to have 'id_' attribute
51-
feature = cls.get_feature_by_id(model.id_)
51+
feature = cls.get_feature_by_attribute_value("id", model.id_, no_geometries=False)
5252
if not feature:
5353
raise FeatureNotFoundError(model.id_, cls.name)
5454
else:
@@ -82,6 +82,10 @@ def feature_from_model(cls, model: Plan) -> QgsFeature:
8282

8383
@classmethod
8484
def model_from_feature(cls, feature: QgsFeature) -> Plan:
85+
general_regulation_features = [
86+
RegulationGroupLayer.get_feature_by_attribute_value("id", group_id)
87+
for group_id in RegulationGroupAssociationLayer.get_group_ids_for_feature(feature["id"], cls.name)
88+
]
8589
return Plan(
8690
geom=feature.geometry(),
8791
name=feature["name"]["fin"],
@@ -94,8 +98,9 @@ def model_from_feature(cls, feature: QgsFeature) -> Plan:
9498
lifecycle_status_id=feature["lifecycle_status_id"],
9599
organisation_id=feature["organisation_id"],
96100
general_regulations=[
97-
RegulationGroupLayer.model_from_feature(RegulationGroupLayer.get_feature_by_id(group_id))
98-
for group_id in RegulationGroupAssociationLayer.get_group_ids_for_feature(feature["id"], cls.name)
101+
RegulationGroupLayer.model_from_feature(feat)
102+
for feat in general_regulation_features
103+
if feat is not None
99104
],
100105
id_=feature["id"],
101106
)
@@ -123,15 +128,18 @@ def feature_from_model(cls, model: PlanFeature, plan_id: str | None = None) -> Q
123128

124129
@classmethod
125130
def model_from_feature(cls, feature: QgsFeature) -> PlanFeature:
131+
regulation_group_features = [
132+
RegulationGroupLayer.get_feature_by_attribute_value("id", group_id)
133+
for group_id in RegulationGroupAssociationLayer.get_group_ids_for_feature(feature["id"], cls.name)
134+
]
126135
return PlanFeature(
127136
geom=feature.geometry(),
128137
type_of_underground_id=feature["type_of_underground_id"],
129138
layer_name=cls.get_from_project().name(),
130139
name=feature["name"]["fin"],
131140
description=feature["description"]["fin"],
132141
regulation_groups=[
133-
RegulationGroupLayer.model_from_feature(RegulationGroupLayer.get_feature_by_id(group_id))
134-
for group_id in RegulationGroupAssociationLayer.get_group_ids_for_feature(feature["id"], cls.name)
142+
RegulationGroupLayer.model_from_feature(feat) for feat in regulation_group_features if feat is not None
135143
],
136144
plan_id=feature["plan_id"],
137145
id_=feature["id"],
@@ -228,9 +236,12 @@ def feature_from(cls, regulation_group_id: str, layer_name: str, feature_id: str
228236
attribute = cls.layer_name_to_attribute_map.get(layer_name)
229237

230238
# Check if association exists to avoid duplicate assocations
231-
for feature in layer.getFeatures():
232-
if feature["plan_regulation_group_id"] == regulation_group_id and feature[attribute] == feature_id:
239+
for feature in cls.get_features_by_attribute_value("plan_regulation_group_id", regulation_group_id):
240+
if feature[attribute] == feature_id:
233241
return None
242+
# for feature in layer.getFeatures():
243+
# if feature["plan_regulation_group_id"] == regulation_group_id and feature[attribute] == feature_id:
244+
# return None
234245

235246
feature = QgsVectorLayerUtils.createFeature(layer)
236247
feature["plan_regulation_group_id"] = regulation_group_id
@@ -243,16 +254,18 @@ def feature_from(cls, regulation_group_id: str, layer_name: str, feature_id: str
243254
return feature
244255

245256
@classmethod
246-
def get_associations_for_feature(cls, feature_id: str, layer_name: str) -> list[QgsFeature]:
257+
def get_associations_for_feature(cls, feature_id: str, layer_name: str) -> Generator[QgsFeature]:
247258
attribute = cls.layer_name_to_attribute_map.get(layer_name)
248-
return [feature for feature in cls.get_features() if feature[attribute] == feature_id]
259+
if not attribute:
260+
raise LayerNotFoundError(layer_name)
261+
return cls.get_features_by_attribute_value(attribute, feature_id)
249262

250263
@classmethod
251-
def get_group_ids_for_feature(cls, feature_id: str, layer_name: str) -> list[str]:
264+
def get_group_ids_for_feature(cls, feature_id: str, layer_name: str) -> Generator[str]:
252265
attribute = cls.layer_name_to_attribute_map.get(layer_name)
253-
return [
254-
feature["plan_regulation_group_id"] for feature in cls.get_features() if feature[attribute] == feature_id
255-
]
266+
if not attribute:
267+
raise LayerNotFoundError(layer_name)
268+
return cls.get_attribute_values_by_another_attribute_value("plan_regulation_group_id", attribute, feature_id)
256269

257270
@classmethod
258271
def get_dangling_associations(
@@ -318,8 +331,8 @@ def model_from_feature(cls, feature: QgsFeature) -> Regulation:
318331
)
319332

320333
@classmethod
321-
def regulations_with_group_id(cls, group_id: str) -> list[QgsFeature]:
322-
return [feat for feat in cls.get_features() if feat["plan_regulation_group_id"] == group_id]
334+
def regulations_with_group_id(cls, group_id: str) -> Generator[QgsFeature]:
335+
return cls.get_features_by_attribute_value("plan_regulation_group_id", group_id)
323336

324337

325338
class PlanPropositionLayer(AbstractPlanLayer):

0 commit comments

Comments
 (0)