better-notion 2.2.0__py3-none-any.whl → 2.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,421 @@
1
+ """Full-text search implementation for the agents SDK.
2
+
3
+ This module provides text-based search functionality across all entity types
4
+ with relevance scoring and filtering capabilities.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from better_notion._sdk.client import NotionClient
13
+
14
+
15
+ class SearchResult:
16
+ """Represents a search result with relevance information.
17
+
18
+ Attributes:
19
+ entity: The entity (Task, Idea, Incident, WorkIssue, etc.)
20
+ relevance: Relevance level ("high", "medium", "low", "none")
21
+ matched_in: List of fields that matched (e.g., ["title", "description"])
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ entity: Any,
27
+ relevance: str,
28
+ matched_in: list[str],
29
+ ) -> None:
30
+ """Initialize a SearchResult.
31
+
32
+ Args:
33
+ entity: The entity that matched
34
+ relevance: Relevance level
35
+ matched_in: Which fields matched the query
36
+ """
37
+ self.entity = entity
38
+ self.relevance = relevance
39
+ self.matched_in = matched_in
40
+
41
+ def to_dict(self) -> dict[str, Any]:
42
+ """Convert search result to dictionary.
43
+
44
+ Returns:
45
+ Dictionary with entity details and search metadata
46
+ """
47
+ result = {
48
+ "id": self.entity.id,
49
+ "title": self.entity.title,
50
+ "status": getattr(self.entity, "status", None),
51
+ "relevance": self.relevance,
52
+ "matched_in": self.matched_in,
53
+ }
54
+
55
+ # Add entity-specific fields
56
+ result.update(self._entity_specific_fields())
57
+
58
+ return result
59
+
60
+ def _entity_specific_fields(self) -> dict[str, Any]:
61
+ """Get entity-specific fields for output.
62
+
63
+ Returns:
64
+ Dictionary of entity-specific properties
65
+ """
66
+ result = {}
67
+
68
+ # Import entity types to check instance
69
+ from better_notion.plugins.official.agents_sdk.models import (
70
+ Idea,
71
+ Incident,
72
+ Task,
73
+ WorkIssue,
74
+ )
75
+
76
+ if isinstance(self.entity, Task):
77
+ result["priority"] = getattr(self.entity, "priority", None)
78
+ result["type"] = getattr(self.entity, "task_type", None)
79
+ elif isinstance(self.entity, Idea):
80
+ result["category"] = getattr(self.entity, "category", None)
81
+ result["effort"] = getattr(self.entity, "effort_estimate", None)
82
+ elif isinstance(self.entity, Incident):
83
+ result["severity"] = getattr(self.entity, "severity", None)
84
+ result["incident_type"] = getattr(self.entity, "incident_type", None)
85
+ elif isinstance(self.entity, WorkIssue):
86
+ result["severity"] = getattr(self.entity, "severity", None)
87
+ result["issue_type"] = getattr(self.entity, "issue_type", None)
88
+
89
+ return result
90
+
91
+
92
+ class TextSearcher:
93
+ """Full-text search implementation for entities.
94
+
95
+ Provides client-side text search with relevance scoring across
96
+ Tasks, Ideas, Incidents, and WorkIssues.
97
+ """
98
+
99
+ def __init__(self, client: NotionClient) -> None:
100
+ """Initialize the TextSearcher.
101
+
102
+ Args:
103
+ client: Notion API client
104
+ """
105
+ self._client = client
106
+
107
+ def _calculate_relevance(
108
+ self,
109
+ query: str,
110
+ title: str,
111
+ description: str | None = None,
112
+ ) -> tuple[str, list[str]]:
113
+ """Calculate relevance score and matched fields.
114
+
115
+ Args:
116
+ query: Search query (lowercase)
117
+ title: Entity title
118
+ description: Entity description (optional)
119
+
120
+ Returns:
121
+ Tuple of (relevance_level, matched_fields)
122
+ - relevance_level: "high", "medium", "low", or "none"
123
+ - matched_fields: List of field names that matched
124
+ """
125
+ query_lower = query.lower()
126
+ title_lower = title.lower()
127
+ matched_in = []
128
+
129
+ # Check title
130
+ title_match = query_lower in title_lower
131
+ if title_match:
132
+ matched_in.append("title")
133
+
134
+ # Check description
135
+ desc_match = False
136
+ if description:
137
+ desc_lower = description.lower()
138
+ desc_match = query_lower in desc_lower
139
+ if desc_match:
140
+ matched_in.append("description")
141
+
142
+ # Calculate relevance
143
+ if not matched_in:
144
+ return "none", []
145
+
146
+ if title_match and desc_match:
147
+ return "high", matched_in
148
+ elif title_match:
149
+ # Exact phrase match or single word match in title = high relevance
150
+ if query_lower == title_lower or query_lower in title_lower.split():
151
+ return "high", matched_in
152
+ return "medium", matched_in
153
+ elif desc_match:
154
+ return "low", matched_in
155
+
156
+ return "none", []
157
+
158
+ async def search_tasks(
159
+ self,
160
+ query: str,
161
+ database_id: str,
162
+ filters: dict[str, Any] | None = None,
163
+ limit: int = 50,
164
+ ) -> list[SearchResult]:
165
+ """Search tasks by text query.
166
+
167
+ Args:
168
+ query: Text search query
169
+ database_id: Tasks database ID
170
+ filters: Optional Notion filters to apply
171
+ limit: Maximum number of results to return
172
+
173
+ Returns:
174
+ List of SearchResults sorted by relevance
175
+ """
176
+ from better_notion.plugins.official.agents_sdk.models import Task
177
+
178
+ # Build query object
179
+ query_obj: dict[str, Any] = {}
180
+ if filters:
181
+ query_obj["filter"] = filters
182
+
183
+ # Query the database
184
+ response = await self._client._api._request(
185
+ "POST",
186
+ f"/databases/{database_id}/query",
187
+ json=query_obj,
188
+ )
189
+
190
+ results = []
191
+ for page_data in response.get("results", []):
192
+ task = Task(self._client, page_data)
193
+
194
+ # Get description if available
195
+ description = None
196
+ if hasattr(task, "description"):
197
+ description = task.description
198
+
199
+ # Calculate relevance
200
+ relevance, matched_in = self._calculate_relevance(query, task.title, description)
201
+
202
+ if relevance != "none":
203
+ results.append(SearchResult(task, relevance, matched_in))
204
+
205
+ # Sort by relevance (high > medium > low) and then by title
206
+ results.sort(
207
+ key=lambda r: (
208
+ {"high": 0, "medium": 1, "low": 2}[r.relevance],
209
+ r.entity.title,
210
+ )
211
+ )
212
+
213
+ return results[:limit]
214
+
215
+ async def search_ideas(
216
+ self,
217
+ query: str,
218
+ database_id: str,
219
+ filters: dict[str, Any] | None = None,
220
+ limit: int = 50,
221
+ ) -> list[SearchResult]:
222
+ """Search ideas by text query.
223
+
224
+ Args:
225
+ query: Text search query
226
+ database_id: Ideas database ID
227
+ filters: Optional Notion filters to apply
228
+ limit: Maximum number of results to return
229
+
230
+ Returns:
231
+ List of SearchResults sorted by relevance
232
+ """
233
+ from better_notion.plugins.official.agents_sdk.models import Idea
234
+
235
+ query_obj: dict[str, Any] = {}
236
+ if filters:
237
+ query_obj["filter"] = filters
238
+
239
+ response = await self._client._api._request(
240
+ "POST",
241
+ f"/databases/{database_id}/query",
242
+ json=query_obj,
243
+ )
244
+
245
+ results = []
246
+ for page_data in response.get("results", []):
247
+ idea = Idea(self._client, page_data)
248
+
249
+ description = None
250
+ if hasattr(idea, "description"):
251
+ description = idea.description
252
+
253
+ relevance, matched_in = self._calculate_relevance(query, idea.title, description)
254
+
255
+ if relevance != "none":
256
+ results.append(SearchResult(idea, relevance, matched_in))
257
+
258
+ results.sort(
259
+ key=lambda r: (
260
+ {"high": 0, "medium": 1, "low": 2}[r.relevance],
261
+ r.entity.title,
262
+ )
263
+ )
264
+
265
+ return results[:limit]
266
+
267
+ async def search_incidents(
268
+ self,
269
+ query: str,
270
+ database_id: str,
271
+ filters: dict[str, Any] | None = None,
272
+ limit: int = 50,
273
+ ) -> list[SearchResult]:
274
+ """Search incidents by text query.
275
+
276
+ Args:
277
+ query: Text search query
278
+ database_id: Incidents database ID
279
+ filters: Optional Notion filters to apply
280
+ limit: Maximum number of results to return
281
+
282
+ Returns:
283
+ List of SearchResults sorted by relevance
284
+ """
285
+ from better_notion.plugins.official.agents_sdk.models import Incident
286
+
287
+ query_obj: dict[str, Any] = {}
288
+ if filters:
289
+ query_obj["filter"] = filters
290
+
291
+ response = await self._client._api._request(
292
+ "POST",
293
+ f"/databases/{database_id}/query",
294
+ json=query_obj,
295
+ )
296
+
297
+ results = []
298
+ for page_data in response.get("results", []):
299
+ incident = Incident(self._client, page_data)
300
+
301
+ description = None
302
+ if hasattr(incident, "description"):
303
+ description = incident.description
304
+
305
+ relevance, matched_in = self._calculate_relevance(query, incident.title, description)
306
+
307
+ if relevance != "none":
308
+ results.append(SearchResult(incident, relevance, matched_in))
309
+
310
+ results.sort(
311
+ key=lambda r: (
312
+ {"high": 0, "medium": 1, "low": 2}[r.relevance],
313
+ r.entity.title,
314
+ )
315
+ )
316
+
317
+ return results[:limit]
318
+
319
+ async def search_work_issues(
320
+ self,
321
+ query: str,
322
+ database_id: str,
323
+ filters: dict[str, Any] | None = None,
324
+ limit: int = 50,
325
+ ) -> list[SearchResult]:
326
+ """Search work issues by text query.
327
+
328
+ Args:
329
+ query: Text search query
330
+ database_id: Work Issues database ID
331
+ filters: Optional Notion filters to apply
332
+ limit: Maximum number of results to return
333
+
334
+ Returns:
335
+ List of SearchResults sorted by relevance
336
+ """
337
+ from better_notion.plugins.official.agents_sdk.models import WorkIssue
338
+
339
+ query_obj: dict[str, Any] = {}
340
+ if filters:
341
+ query_obj["filter"] = filters
342
+
343
+ response = await self._client._api._request(
344
+ "POST",
345
+ f"/databases/{database_id}/query",
346
+ json=query_obj,
347
+ )
348
+
349
+ results = []
350
+ for page_data in response.get("results", []):
351
+ issue = WorkIssue(self._client, page_data)
352
+
353
+ description = None
354
+ if hasattr(issue, "description"):
355
+ description = issue.description
356
+
357
+ relevance, matched_in = self._calculate_relevance(query, issue.title, description)
358
+
359
+ if relevance != "none":
360
+ results.append(SearchResult(issue, relevance, matched_in))
361
+
362
+ results.sort(
363
+ key=lambda r: (
364
+ {"high": 0, "medium": 1, "low": 2}[r.relevance],
365
+ r.entity.title,
366
+ )
367
+ )
368
+
369
+ return results[:limit]
370
+
371
+ async def search_all(
372
+ self,
373
+ query: str,
374
+ workspace_config: dict[str, Any],
375
+ limit_per_type: int = 10,
376
+ ) -> dict[str, Any]:
377
+ """Search across all entity types.
378
+
379
+ Args:
380
+ query: Text search query
381
+ workspace_config: Workspace configuration with database IDs
382
+ limit_per_type: Maximum results per entity type
383
+
384
+ Returns:
385
+ Dictionary with results for each entity type and total count
386
+ """
387
+ results = {
388
+ "tasks": [],
389
+ "ideas": [],
390
+ "incidents": [],
391
+ "work_issues": [],
392
+ }
393
+
394
+ # Search tasks
395
+ tasks_db = workspace_config.get("Tasks")
396
+ if tasks_db:
397
+ task_results = await self.search_tasks(query, tasks_db, limit=limit_per_type)
398
+ results["tasks"] = [r.to_dict() for r in task_results]
399
+
400
+ # Search ideas
401
+ ideas_db = workspace_config.get("Ideas")
402
+ if ideas_db:
403
+ idea_results = await self.search_ideas(query, ideas_db, limit=limit_per_type)
404
+ results["ideas"] = [r.to_dict() for r in idea_results]
405
+
406
+ # Search incidents
407
+ incidents_db = workspace_config.get("Incidents")
408
+ if incidents_db:
409
+ incident_results = await self.search_incidents(query, incidents_db, limit=limit_per_type)
410
+ results["incidents"] = [r.to_dict() for r in incident_results]
411
+
412
+ # Search work issues
413
+ issues_db = workspace_config.get("Work_issues")
414
+ if issues_db:
415
+ issue_results = await self.search_work_issues(query, issues_db, limit=limit_per_type)
416
+ results["work_issues"] = [r.to_dict() for r in issue_results]
417
+
418
+ total = sum(len(v) for v in results.values() if isinstance(v, list))
419
+ results["total"] = total
420
+
421
+ return results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: better-notion
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: A high-level Python SDK for the Notion API with developer experience in mind.
5
5
  Project-URL: Homepage, https://github.com/nesalia-inc/better-notion
6
6
  Project-URL: Documentation, https://github.com/nesalia-inc/better-notion#readme
@@ -112,14 +112,17 @@ better_notion/plugins/base.py,sha256=3h9jOZzS--UqmVW3RREtcQ2h1GTWWPUryTencsJKhTM
112
112
  better_notion/plugins/loader.py,sha256=zCWsMdJyvZs1IHFm0zjEiqm_l_5jB1Uw4x30Kq8rLS4,9527
113
113
  better_notion/plugins/state.py,sha256=jH_tZWvC35hqLO4qwl2Kwq9ziWVavwCEUcCqy3s5wMY,3780
114
114
  better_notion/plugins/official/__init__.py,sha256=rPg5vdk1cEANVstMPzxcWmImtsOpdSR40JSml7h1uUk,426
115
- better_notion/plugins/official/agents.py,sha256=usvB2dH1oQ_BUePg2D1nSlHUvk58K3Eq4cdbFG5Fb7Q,54191
116
- better_notion/plugins/official/agents_cli.py,sha256=WFI-Sg2X4kyqyrB7SdaxwpKRGxsMxe1-aj3YU0eXb94,62644
115
+ better_notion/plugins/official/agents.py,sha256=Obx5tkjsd79Dz9coctqJ5RgrlvKIGQLc3DjkGX7iDak,58609
116
+ better_notion/plugins/official/agents_cli.py,sha256=IYnWUIhdUUY8IZFKJlwh9aNXOOzABLAGgeSbfElswjI,74809
117
117
  better_notion/plugins/official/agents_schema.py,sha256=NQRAJFoBAXRuxB9_9Eaf-4tVth-1OZh7GjmN56Yp9lA,39867
118
118
  better_notion/plugins/official/productivity.py,sha256=_-whP4pYA4HufE1aUFbIdhrjU-O9njI7xUO_Id2M1J8,8726
119
119
  better_notion/plugins/official/agents_sdk/__init__.py,sha256=luQBzZLsJ7fC5U0jFu8dfzMviiXj2SBZXcTohM53wkQ,725
120
- better_notion/plugins/official/agents_sdk/managers.py,sha256=EvEZAsH4-RzKh7o8OV_o8a0mRgMCxnqwyiwuj-Y1Cag,41235
120
+ better_notion/plugins/official/agents_sdk/agent.py,sha256=qOX4hsH9VZ-GJH31g6huRQQzyzG7QRD0mXQaebXlf_o,17036
121
+ better_notion/plugins/official/agents_sdk/history.py,sha256=W5xPuo8lWU5g1kDGOtqbi3h4-_G4vNZPea1ecL5bp14,21111
122
+ better_notion/plugins/official/agents_sdk/managers.py,sha256=GICsrV2huEGmZ_kpUjpmBArzC4c-NMzPqm1AgsGg_ek,54693
121
123
  better_notion/plugins/official/agents_sdk/models.py,sha256=vBSqf-9Q-gX14Z8779Nacr8yTI3WMi5cLtkJjONiPoY,91967
122
124
  better_notion/plugins/official/agents_sdk/plugin.py,sha256=bs9O8Unv6SARGj4lBU5Gj9HGbLTUNqTacJ3RLUhdbI4,4479
125
+ better_notion/plugins/official/agents_sdk/search.py,sha256=MCfkxm7cjYH-im__bOMq8duTNEbRqmH8PV4uCVSjElA,13401
123
126
  better_notion/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
127
  better_notion/utils/helpers.py,sha256=HgFuUQlG_HzBOB0z2GA9RxPLoXgwRc0DIxa9Fg6C-Jk,2337
125
128
  better_notion/utils/retry.py,sha256=9WJDNitiIfVTL18hIlipvOKn41ukePrOwtAwx-LevpQ,7479
@@ -133,8 +136,8 @@ better_notion/utils/agents/rbac.py,sha256=8ZA8Y7wbOiVZDbpjpH7iC35SZrZ0jl4fcJ3xWC
133
136
  better_notion/utils/agents/schemas.py,sha256=RM6XPNA_CdtlNgKAFpN0p0Ppqqo6849RX37ODBp088o,22956
134
137
  better_notion/utils/agents/state_machine.py,sha256=xUBEeDTbU1Xq-rsRo2sbr6AUYpYrV9DTHOtZT2cWES8,6699
135
138
  better_notion/utils/agents/workspace.py,sha256=2_dK58fifMqSEifFxThI7ir_1UR9KLN-d6R4CkdNqjQ,26538
136
- better_notion-2.2.0.dist-info/METADATA,sha256=uj6RsRMipVhH2v4gKaSjOMKSgdd0jxT_VClz62l7NY0,11096
137
- better_notion-2.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
138
- better_notion-2.2.0.dist-info/entry_points.txt,sha256=D0bUcP7Z00Zyjxw7r2p29T95UrwioDO0aGDoHe9I6fo,55
139
- better_notion-2.2.0.dist-info/licenses/LICENSE,sha256=BAdN3JpgMY_y_fWqZSCFSvSbC2mTHP-BKDAzF5FXQAI,1069
140
- better_notion-2.2.0.dist-info/RECORD,,
139
+ better_notion-2.3.1.dist-info/METADATA,sha256=SQ75uKGrHKNO42NJYadBPTMYJVRoTgqlOpzDIkfVk0c,11096
140
+ better_notion-2.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
141
+ better_notion-2.3.1.dist-info/entry_points.txt,sha256=D0bUcP7Z00Zyjxw7r2p29T95UrwioDO0aGDoHe9I6fo,55
142
+ better_notion-2.3.1.dist-info/licenses/LICENSE,sha256=BAdN3JpgMY_y_fWqZSCFSvSbC2mTHP-BKDAzF5FXQAI,1069
143
+ better_notion-2.3.1.dist-info/RECORD,,