Coverage for gws-app/gws/base/search/finder.py: 77%

73 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Base finder.""" 

2 

3from typing import Optional, cast 

4 

5import gws 

6import gws.base.model 

7import gws.base.template 

8import gws.lib.shape 

9import gws.config.util 

10import gws.lib.uom 

11 

12 

13class SpatialContext(gws.Enum): 

14 """Area searched by keyword searches without a user geometry.""" 

15 

16 map = 'map' 

17 """Search the whole map extent.""" 

18 view = 'view' 

19 """Search the extent currently visible in the client.""" 

20 

21 

22class Config(gws.ConfigWithAccess): 

23 """Finder configuration.""" 

24 

25 models: Optional[list[gws.ext.config.model]] 

26 """Models used to read the features.""" 

27 spatialContext: Optional[SpatialContext] = SpatialContext.map 

28 """Area searched by keyword searches without a user geometry.""" 

29 templates: Optional[list[gws.ext.config.template]] 

30 """Templates to format the found features.""" 

31 title: Optional[str] 

32 """Finder title.""" 

33 category: Optional[str] 

34 """Category assigned to the results.""" 

35 withGeometry: bool = True 

36 """Use this finder for geometry searches.""" 

37 withKeyword: bool = True 

38 """Use this finder for keyword searches.""" 

39 withFilter: bool = True 

40 """Use this finder for filter searches.""" 

41 

42 

43class Object(gws.Finder): 

44 """Base class for finders. 

45 

46 Reads the common finder options and decides whether a query can be run. Runs 

47 a query by reading the features through a model, within the area given by 

48 ``context_shape``. Subclasses declare which kinds of search they support and 

49 configure their models and templates, using ``configure_models`` and 

50 ``configure_templates``. 

51 """ 

52 

53 spatialContext: SpatialContext 

54 """Area searched by keyword searches without a user geometry.""" 

55 

56 def configure(self): 

57 self.templates = [] 

58 self.models = [] 

59 

60 self.withKeyword = self.cfg('withKeyword', default=True) 

61 self.withGeometry = self.cfg('withGeometry', default=True) 

62 self.withFilter = self.cfg('withFilter', default=True) 

63 

64 self.spatialContext = self.cfg('spatialContext', default=SpatialContext.map) 

65 self.title = self.cfg('title', default='') 

66 self.category = self.cfg('category', default='') 

67 

68 ## 

69 

70 def configure_models(self): 

71 """Create the models from the ``models`` config. 

72 

73 Returns: 

74 ``True`` if models are configured. 

75 """ 

76 return gws.config.util.configure_models_for(self) 

77 

78 def configure_templates(self): 

79 """Create the templates from the ``templates`` config. 

80 

81 Returns: 

82 ``True`` if templates are configured. 

83 """ 

84 return gws.config.util.configure_templates_for(self) 

85 

86 ## 

87 

88 def can_run(self, search, user): 

89 has_param = False 

90 

91 if search.keyword: 

92 if not self.supportsKeywordSearch or not self.withKeyword: 

93 gws.log.debug(f'can run: False: {self} {search.keyword=} {self.supportsKeywordSearch=} {self.withKeyword=}') 

94 return False 

95 has_param = True 

96 

97 if search.shape: 

98 if not self.supportsGeometrySearch or not self.withGeometry: 

99 gws.log.debug(f'can run: False: {self} <shape> {self.supportsGeometrySearch=} {self.withGeometry=}') 

100 return False 

101 has_param = True 

102 

103 if search.filter: 

104 if not self.supportsFilterSearch or not self.withFilter: 

105 gws.log.debug(f'can run: False: {self} {search.filter=} {self.supportsFilterSearch=} {self.withFilter=}') 

106 return False 

107 has_param = True 

108 

109 return has_param 

110 

111 def context_shape(self, search: gws.SearchQuery) -> gws.Shape: 

112 """Return the shape to search in. 

113 

114 This is the query shape if given. Otherwise, it is the query bounds if 

115 ``spatialContext`` is ``view``, or the bounds of the project map. 

116 

117 Args: 

118 search: Search query. 

119 

120 Returns: 

121 The search shape, or ``None`` if there is none. 

122 """ 

123 if search.shape: 

124 return search.shape 

125 if self.spatialContext == SpatialContext.view and search.bounds: 

126 return gws.lib.shape.from_bounds(search.bounds) 

127 if search.project: 

128 return gws.lib.shape.from_bounds(search.project.map.bounds) 

129 

130 def run(self, search, user, layer=None): 

131 model = self.root.app.modelMgr.find_model(self, layer, user=user, access=gws.Access.read) 

132 if not model: 

133 gws.log.debug(f'no model for {user.uid=} in finder {self.uid!r}') 

134 return [] 

135 search = cast(gws.SearchQuery, gws.u.merge(search, shape=self.context_shape(search))) 

136 mc = gws.ModelContext(op=gws.ModelOperation.read, target=gws.ModelReadTarget.searchResults, user=user) 

137 return model.find_features(search, mc)