better-notion 1.0.1__py3-none-any.whl → 1.1.0__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.
@@ -99,6 +99,11 @@ class NotionClient:
99
99
  self._comment_cache: Cache[object] = Cache()
100
100
  # No cache for Block (too many)
101
101
 
102
+ # Plugin-managed resources (for SDK plugin system)
103
+ self._plugin_caches: dict[str, Cache[object]] = {}
104
+ self._plugin_managers: dict[str, object] = {}
105
+ self._plugin_models: dict[str, type] = {}
106
+
102
107
  # Search cache
103
108
  self._search_cache: dict[str, list[object]] = {}
104
109
 
@@ -259,10 +264,92 @@ class NotionClient:
259
264
 
260
265
  return results
261
266
 
267
+ # ===== SDK PLUGIN SYSTEM =====
268
+
269
+ def register_sdk_plugin(
270
+ self,
271
+ models: dict[str, type] | None = None,
272
+ caches: dict[str, "Cache"] | None = None,
273
+ managers: dict[str, object] | None = None,
274
+ ) -> None:
275
+ """Register an SDK plugin's resources with the client.
276
+
277
+ This method allows plugins to register custom models, caches, and
278
+ managers without modifying the core SDK.
279
+
280
+ Args:
281
+ models: Dict mapping model names to model classes
282
+ caches: Dict mapping cache names to Cache instances
283
+ managers: Dict mapping manager names to manager instances
284
+
285
+ Example:
286
+ >>> client.register_sdk_plugin(
287
+ ... models={"Organization": Organization},
288
+ ... caches={"organizations": Cache()},
289
+ ... managers={"organizations": OrganizationManager(client)},
290
+ ... )
291
+ """
292
+ if models:
293
+ self._plugin_models.update(models)
294
+
295
+ if caches:
296
+ self._plugin_caches.update(caches)
297
+
298
+ if managers:
299
+ self._plugin_managers.update(managers)
300
+
301
+ def plugin_cache(self, name: str) -> "Cache | None":
302
+ """Access a plugin-registered cache.
303
+
304
+ Args:
305
+ name: Cache name (e.g., "organizations")
306
+
307
+ Returns:
308
+ Cache instance if found, None otherwise
309
+
310
+ Example:
311
+ >>> cache = client.plugin_cache("organizations")
312
+ >>> if cache and org_id in cache:
313
+ ... org = cache[org_id]
314
+ """
315
+ return self._plugin_caches.get(name)
316
+
317
+ def plugin_manager(self, name: str) -> object | None:
318
+ """Access a plugin-registered manager.
319
+
320
+ Args:
321
+ name: Manager name (e.g., "organizations")
322
+
323
+ Returns:
324
+ Manager instance if found, None otherwise
325
+
326
+ Example:
327
+ >>> orgs_mgr = client.plugin_manager("organizations")
328
+ >>> if orgs_mgr:
329
+ ... orgs = await orgs_mgr.list()
330
+ """
331
+ return self._plugin_managers.get(name)
332
+
333
+ def plugin_model(self, name: str) -> type | None:
334
+ """Access a plugin-registered model class.
335
+
336
+ Args:
337
+ name: Model name (e.g., "Organization")
338
+
339
+ Returns:
340
+ Model class if found, None otherwise
341
+
342
+ Example:
343
+ >>> OrgClass = client.plugin_model("Organization")
344
+ >>> if OrgClass:
345
+ ... org = await OrgClass.get(org_id, client=client)
346
+ """
347
+ return self._plugin_models.get(name)
348
+
262
349
  # ===== CACHE MANAGEMENT =====
263
350
 
264
351
  def clear_all_caches(self) -> None:
265
- """Clear all caches.
352
+ """Clear all caches including plugin caches.
266
353
 
267
354
  Example:
268
355
  >>> client.clear_all_caches()
@@ -272,8 +359,12 @@ class NotionClient:
272
359
  self._page_cache.clear()
273
360
  self._search_cache.clear()
274
361
 
362
+ # Clear plugin caches
363
+ for cache in self._plugin_caches.values():
364
+ cache.clear()
365
+
275
366
  def get_cache_stats(self) -> dict[str, dict[str, Any]]:
276
- """Get statistics for all caches.
367
+ """Get statistics for all caches including plugin caches.
277
368
 
278
369
  Returns:
279
370
  Dict with stats for each cache
@@ -283,10 +374,11 @@ class NotionClient:
283
374
  >>> print(stats)
284
375
  {
285
376
  'user_cache': {'hits': 100, 'misses': 5, 'size': 50, 'hit_rate': 0.95},
377
+ 'plugin:organizations': {'hits': 50, 'misses': 2, 'size': 10, 'hit_rate': 0.96},
286
378
  ...
287
379
  }
288
380
  """
289
- return {
381
+ stats = {
290
382
  "user_cache": {
291
383
  "hits": self._user_cache.stats.hits,
292
384
  "misses": self._user_cache.stats.misses,
@@ -310,6 +402,17 @@ class NotionClient:
310
402
  }
311
403
  }
312
404
 
405
+ # Add plugin cache stats
406
+ for cache_name, cache in self._plugin_caches.items():
407
+ stats[f"plugin:{cache_name}"] = {
408
+ "hits": cache.stats.hits,
409
+ "misses": cache.stats.misses,
410
+ "size": cache.stats.size,
411
+ "hit_rate": cache.stats.hit_rate
412
+ }
413
+
414
+ return stats
415
+
313
416
  # ===== CONTEXT MANAGER =====
314
417
 
315
418
  async def __aenter__(self):
@@ -0,0 +1,180 @@
1
+ """SDK Plugin System for extending NotionClient functionality.
2
+
3
+ This module provides the protocol interface for SDK-level plugins that need to:
4
+ - Register custom model classes
5
+ - Add dedicated caches to NotionClient
6
+ - Create custom managers
7
+ - Initialize plugin-specific resources
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any, Protocol
13
+
14
+ if TYPE_CHECKING:
15
+ from better_notion._sdk.cache import Cache
16
+ from better_notion._sdk.client import NotionClient
17
+ from better_notion._sdk.base.entity import BaseEntity
18
+
19
+
20
+ class SDKPluginInterface(Protocol):
21
+ """
22
+ Protocol for SDK-level plugins that extend NotionClient functionality.
23
+
24
+ SDK plugins can register custom models, caches, and managers with the
25
+ NotionClient, enabling domain-specific extensions without modifying
26
+ the core SDK.
27
+
28
+ Example:
29
+ >>> class AgentsSDKPlugin:
30
+ ... def register_models(self) -> dict[str, type[BaseEntity]]:
31
+ ... return {"Organization": Organization}
32
+ ...
33
+ ... def register_caches(self, client: NotionClient) -> dict[str, Cache]:
34
+ ... return {"organizations": Cache()}
35
+ ...
36
+ ... def register_managers(self, client: NotionClient) -> dict[str, Any]:
37
+ ... return {"organizations": OrganizationManager(client)}
38
+ """
39
+
40
+ def register_models(self) -> dict[str, type[BaseEntity]]:
41
+ """Register custom model classes.
42
+
43
+ Model classes should inherit from BaseEntity and follow the
44
+ autonomous entity pattern (get, create, update methods).
45
+
46
+ Returns:
47
+ Dictionary mapping model names to model classes
48
+
49
+ Example:
50
+ >>> def register_models(self) -> dict[str, type[BaseEntity]]:
51
+ ... return {
52
+ ... "Organization": Organization,
53
+ ... "Project": Project,
54
+ ... "Task": Task,
55
+ ... }
56
+ """
57
+ ...
58
+
59
+ def register_caches(self, client: "NotionClient") -> dict[str, "Cache"]:
60
+ """Register custom caches with the client.
61
+
62
+ Caches are stored in the client and accessible via
63
+ client.plugin_cache(name). Each cache should have a unique name
64
+ to avoid collisions with other plugins.
65
+
66
+ Args:
67
+ client: NotionClient instance to register caches with
68
+
69
+ Returns:
70
+ Dictionary mapping cache names to Cache instances
71
+
72
+ Example:
73
+ >>> def register_caches(self, client) -> dict[str, Cache]:
74
+ ... return {
75
+ ... "organizations": Cache(),
76
+ ... "projects": Cache(),
77
+ ... "tasks": Cache(),
78
+ ... }
79
+ """
80
+ ...
81
+
82
+ def register_managers(self, client: "NotionClient") -> dict[str, Any]:
83
+ """Register custom managers with the client.
84
+
85
+ Managers are accessible via client.plugin_manager(name).
86
+ They typically provide convenience methods for working with
87
+ the plugin's models.
88
+
89
+ Args:
90
+ client: NotionClient instance
91
+
92
+ Returns:
93
+ Dictionary mapping manager names to manager instances
94
+
95
+ Example:
96
+ >>> def register_managers(self, client) -> dict[str, Any]:
97
+ ... return {
98
+ ... "organizations": OrganizationManager(client),
99
+ ... "projects": ProjectManager(client),
100
+ ... }
101
+ """
102
+ ...
103
+
104
+ def initialize(self, client: "NotionClient") -> None:
105
+ """Initialize plugin-specific resources.
106
+
107
+ Called after all registrations (models, caches, managers) are
108
+ complete. Use this for setup tasks like loading configuration,
109
+ validating resources, or establishing connections.
110
+
111
+ Args:
112
+ client: NotionClient instance
113
+
114
+ Example:
115
+ >>> def initialize(self, client) -> None:
116
+ ... # Load database IDs from config
117
+ ... db_ids = load_workspace_config()
118
+ ... client._workspace_config = db_ids
119
+ """
120
+ ...
121
+
122
+ def get_info(self) -> dict[str, Any]:
123
+ """Return plugin metadata.
124
+
125
+ Returns:
126
+ Dictionary with plugin information
127
+
128
+ Example:
129
+ >>> def get_info(self) -> dict[str, Any]:
130
+ ... return {
131
+ ... "name": "agents-sdk",
132
+ ... "version": "1.0.0",
133
+ ... "description": "SDK extensions for agents workflow",
134
+ ... }
135
+ """
136
+ ...
137
+
138
+
139
+ class CombinedPluginInterface(Protocol):
140
+ """
141
+ Protocol for plugins that extend both CLI and SDK.
142
+
143
+ This allows a single plugin class to provide:
144
+ - CLI commands (via register_commands)
145
+ - SDK extensions (via register_models, register_caches, etc.)
146
+
147
+ Example:
148
+ >>> class AgentsPlugin(CombinedPluginInterface):
149
+ ... def register_commands(self, app: typer.Typer) -> None:
150
+ ... # Register CLI commands
151
+ ... pass
152
+ ...
153
+ ... def register_models(self) -> dict[str, type[BaseEntity]]:
154
+ ... # Register SDK models
155
+ ... return {"Organization": Organization}
156
+ """
157
+
158
+ def register_commands(self, app: Any) -> None:
159
+ """Register CLI commands with Typer app."""
160
+ ...
161
+
162
+ def register_models(self) -> dict[str, type[BaseEntity]]:
163
+ """Register custom model classes."""
164
+ ...
165
+
166
+ def register_caches(self, client: "NotionClient") -> dict[str, "Cache"]:
167
+ """Register custom caches."""
168
+ ...
169
+
170
+ def register_managers(self, client: "NotionClient") -> dict[str, Any]:
171
+ """Register custom managers."""
172
+ ...
173
+
174
+ def initialize(self, client: "NotionClient") -> None:
175
+ """Initialize plugin resources."""
176
+ ...
177
+
178
+ def get_info(self) -> dict[str, Any]:
179
+ """Return plugin metadata."""
180
+ ...
@@ -5,14 +5,20 @@ Plugins can extend the CLI by:
5
5
  1. Adding new commands (CommandPlugin)
6
6
  2. Providing data filters/formatters (DataPlugin)
7
7
  3. Combining both capabilities
8
+ 4. Extending the SDK with models, caches, and managers (SDKPlugin)
8
9
  """
9
10
  from __future__ import annotations
10
11
 
11
12
  from abc import ABC, abstractmethod
12
- from typing import Any, Protocol
13
+ from typing import TYPE_CHECKING, Any, Protocol
13
14
 
14
15
  import typer
15
16
 
17
+ if TYPE_CHECKING:
18
+ from better_notion._sdk.cache import Cache
19
+ from better_notion._sdk.client import NotionClient
20
+ from better_notion._sdk.base.entity import BaseEntity
21
+
16
22
 
17
23
  class CommandPlugin(Protocol):
18
24
  """
@@ -148,6 +154,98 @@ class PluginInterface(ABC):
148
154
  """
149
155
  return {}
150
156
 
157
+ # ===== SDK EXTENSION METHODS (OPTIONAL) =====
158
+
159
+ def register_sdk_models(self) -> dict[str, type[BaseEntity]]:
160
+ """
161
+ Register custom SDK model classes.
162
+
163
+ Plugins can register domain-specific models that extend BaseEntity.
164
+ These models are accessible via client.plugin_model(name).
165
+
166
+ Default implementation returns empty dict.
167
+
168
+ Returns:
169
+ Dictionary mapping model names to model classes
170
+
171
+ Example:
172
+ >>> def register_sdk_models(self) -> dict[str, type[BaseEntity]]:
173
+ ... return {
174
+ ... "Organization": Organization,
175
+ ... "Project": Project,
176
+ ... }
177
+ """
178
+ return {}
179
+
180
+ def register_sdk_caches(self, client: "NotionClient") -> dict[str, "Cache"]:
181
+ """
182
+ Register custom SDK caches.
183
+
184
+ Plugins can register dedicated caches for their models.
185
+ These caches are accessible via client.plugin_cache(name).
186
+
187
+ Default implementation returns empty dict.
188
+
189
+ Args:
190
+ client: NotionClient instance
191
+
192
+ Returns:
193
+ Dictionary mapping cache names to Cache instances
194
+
195
+ Example:
196
+ >>> def register_sdk_caches(self, client) -> dict[str, Cache]:
197
+ ... return {
198
+ ... "organizations": Cache(),
199
+ ... "projects": Cache(),
200
+ ... }
201
+ """
202
+ return {}
203
+
204
+ def register_sdk_managers(self, client: "NotionClient") -> dict[str, Any]:
205
+ """
206
+ Register custom SDK managers.
207
+
208
+ Plugins can register managers that provide convenience methods
209
+ for working with their models. Managers are accessible via
210
+ client.plugin_manager(name).
211
+
212
+ Default implementation returns empty dict.
213
+
214
+ Args:
215
+ client: NotionClient instance
216
+
217
+ Returns:
218
+ Dictionary mapping manager names to manager instances
219
+
220
+ Example:
221
+ >>> def register_sdk_managers(self, client) -> dict[str, Any]:
222
+ ... return {
223
+ ... "organizations": OrganizationManager(client),
224
+ ... }
225
+ """
226
+ return {}
227
+
228
+ def sdk_initialize(self, client: "NotionClient") -> None:
229
+ """
230
+ Initialize SDK plugin resources.
231
+
232
+ Called after all registrations are complete. Use this for
233
+ setup tasks like loading configuration, validating resources,
234
+ or establishing connections.
235
+
236
+ Default implementation does nothing.
237
+
238
+ Args:
239
+ client: NotionClient instance
240
+
241
+ Example:
242
+ >>> def sdk_initialize(self, client) -> None:
243
+ ... # Load workspace configuration
244
+ ... config = load_workspace_config()
245
+ ... client._workspace_config = config
246
+ """
247
+ pass
248
+
151
249
  def validate(self) -> tuple[bool, list[str]]:
152
250
  """
153
251
  Validate the plugin structure and configuration.
@@ -13,10 +13,13 @@ import importlib.util
13
13
  import json
14
14
  import sys
15
15
  from pathlib import Path
16
- from typing import Any
16
+ from typing import TYPE_CHECKING, Any
17
17
 
18
18
  from better_notion.plugins.base import CommandPlugin
19
19
 
20
+ if TYPE_CHECKING:
21
+ from better_notion._sdk.client import NotionClient
22
+
20
23
 
21
24
  class PluginLoader:
22
25
  """
@@ -242,3 +245,50 @@ class PluginLoader:
242
245
  return True
243
246
 
244
247
  return False
248
+
249
+ def register_sdk_extensions(self, client: "NotionClient") -> None:
250
+ """
251
+ Register SDK extensions from all loaded plugins with a NotionClient.
252
+
253
+ This method iterates through all loaded plugins and registers their
254
+ SDK models, caches, and managers with the provided NotionClient instance.
255
+
256
+ Args:
257
+ client: NotionClient instance to register extensions with
258
+
259
+ Example:
260
+ >>> loader = PluginLoader()
261
+ >>> loader.discover() # Load plugins first
262
+ >>> client = NotionClient(auth="...")
263
+ >>> loader.register_sdk_extensions(client)
264
+ """
265
+ # Get all loaded plugins
266
+ plugins = list(self.loaded_plugins.values())
267
+
268
+ # Also discover plugins if not already loaded
269
+ if not plugins:
270
+ plugins = self.discover()
271
+
272
+ # Register SDK extensions from each plugin
273
+ for plugin in plugins:
274
+ try:
275
+ # Register models
276
+ models = plugin.register_sdk_models()
277
+ if models:
278
+ client._plugin_models.update(models)
279
+
280
+ # Register caches
281
+ caches = plugin.register_sdk_caches(client)
282
+ if caches:
283
+ client._plugin_caches.update(caches)
284
+
285
+ # Register managers
286
+ managers = plugin.register_sdk_managers(client)
287
+ if managers:
288
+ client._plugin_managers.update(managers)
289
+
290
+ # Initialize plugin
291
+ plugin.sdk_initialize(client)
292
+ except Exception:
293
+ # Continue with other plugins if one fails
294
+ continue
@@ -4,12 +4,10 @@ Official plugins for Better Notion CLI.
4
4
  This package contains officially maintained plugins that extend
5
5
  the CLI with commonly-needed functionality.
6
6
  """
7
- from better_notion.plugins.official.agents import AgentsPlugin
8
7
  from better_notion.plugins.official.productivity import ProductivityPlugin
9
8
 
10
- __all__ = ["AgentsPlugin", "ProductivityPlugin"]
9
+ __all__ = ["ProductivityPlugin"]
11
10
 
12
11
  OFFICIAL_PLUGINS = [
13
- AgentsPlugin,
14
12
  ProductivityPlugin,
15
13
  ]