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
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Base finder."""
3from typing import Optional, cast
5import gws
6import gws.base.model
7import gws.base.template
8import gws.lib.shape
9import gws.config.util
10import gws.lib.uom
13class SpatialContext(gws.Enum):
14 """Area searched by keyword searches without a user geometry."""
16 map = 'map'
17 """Search the whole map extent."""
18 view = 'view'
19 """Search the extent currently visible in the client."""
22class Config(gws.ConfigWithAccess):
23 """Finder configuration."""
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."""
43class Object(gws.Finder):
44 """Base class for finders.
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 """
53 spatialContext: SpatialContext
54 """Area searched by keyword searches without a user geometry."""
56 def configure(self):
57 self.templates = []
58 self.models = []
60 self.withKeyword = self.cfg('withKeyword', default=True)
61 self.withGeometry = self.cfg('withGeometry', default=True)
62 self.withFilter = self.cfg('withFilter', default=True)
64 self.spatialContext = self.cfg('spatialContext', default=SpatialContext.map)
65 self.title = self.cfg('title', default='')
66 self.category = self.cfg('category', default='')
68 ##
70 def configure_models(self):
71 """Create the models from the ``models`` config.
73 Returns:
74 ``True`` if models are configured.
75 """
76 return gws.config.util.configure_models_for(self)
78 def configure_templates(self):
79 """Create the templates from the ``templates`` config.
81 Returns:
82 ``True`` if templates are configured.
83 """
84 return gws.config.util.configure_templates_for(self)
86 ##
88 def can_run(self, search, user):
89 has_param = False
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
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
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
109 return has_param
111 def context_shape(self, search: gws.SearchQuery) -> gws.Shape:
112 """Return the shape to search in.
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.
117 Args:
118 search: Search query.
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)
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)