nextpy-framework 1.0.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.
- nextpy/__init__.py +50 -0
- nextpy/auth.py +94 -0
- nextpy/builder.py +123 -0
- nextpy/cli.py +490 -0
- nextpy/components/__init__.py +45 -0
- nextpy/components/feedback.py +210 -0
- nextpy/components/form.py +346 -0
- nextpy/components/head.py +167 -0
- nextpy/components/hooks_provider.py +64 -0
- nextpy/components/image.py +180 -0
- nextpy/components/layout.py +206 -0
- nextpy/components/link.py +132 -0
- nextpy/components/loader.py +65 -0
- nextpy/components/toast.py +101 -0
- nextpy/components/visual.py +185 -0
- nextpy/config.py +75 -0
- nextpy/core/__init__.py +21 -0
- nextpy/core/builder.py +237 -0
- nextpy/core/data_fetching.py +221 -0
- nextpy/core/renderer.py +252 -0
- nextpy/core/router.py +233 -0
- nextpy/core/sync.py +34 -0
- nextpy/db.py +121 -0
- nextpy/dev_server.py +69 -0
- nextpy/dev_tools.py +157 -0
- nextpy/errors.py +70 -0
- nextpy/hooks.py +348 -0
- nextpy/performance.py +78 -0
- nextpy/plugins.py +61 -0
- nextpy/py.typed +0 -0
- nextpy/server/__init__.py +6 -0
- nextpy/server/app.py +325 -0
- nextpy/server/debug.py +93 -0
- nextpy/server/middleware.py +88 -0
- nextpy/utils/__init__.py +0 -0
- nextpy/utils/cache.py +89 -0
- nextpy/utils/email.py +59 -0
- nextpy/utils/file_upload.py +65 -0
- nextpy/utils/logging.py +52 -0
- nextpy/utils/search.py +59 -0
- nextpy/utils/seo.py +85 -0
- nextpy/utils/validators.py +58 -0
- nextpy/websocket.py +76 -0
- nextpy_framework-1.0.0.dist-info/METADATA +343 -0
- nextpy_framework-1.0.0.dist-info/RECORD +49 -0
- nextpy_framework-1.0.0.dist-info/WHEEL +5 -0
- nextpy_framework-1.0.0.dist-info/entry_points.txt +2 -0
- nextpy_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- nextpy_framework-1.0.0.dist-info/top_level.txt +1 -0
nextpy/hooks.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""
|
|
2
|
+
React-like Hooks for NextPy State Management
|
|
3
|
+
Implements useState, useEffect, useContext, useReducer, useCallback, useMemo, etc.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar, Generic, Tuple
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from functools import wraps
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
T = TypeVar('T')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class HookState:
|
|
16
|
+
"""Manages state for a component"""
|
|
17
|
+
values: Dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
effects: Dict[str, Callable] = field(default_factory=dict)
|
|
19
|
+
effect_dependencies: Dict[str, List] = field(default_factory=dict)
|
|
20
|
+
callbacks: Dict[str, Callable] = field(default_factory=dict)
|
|
21
|
+
callback_dependencies: Dict[str, List] = field(default_factory=dict)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StateManager:
|
|
25
|
+
"""Global state manager for all hooks"""
|
|
26
|
+
|
|
27
|
+
_instance = None
|
|
28
|
+
_states: Dict[str, HookState] = {}
|
|
29
|
+
_current_component: Optional[str] = None
|
|
30
|
+
_hook_index: Dict[str, int] = {}
|
|
31
|
+
|
|
32
|
+
def __new__(cls):
|
|
33
|
+
if cls._instance is None:
|
|
34
|
+
cls._instance = super().__new__(cls)
|
|
35
|
+
return cls._instance
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def set_component(cls, component_id: Optional[str]):
|
|
39
|
+
"""Set current component context"""
|
|
40
|
+
cls._current_component = component_id
|
|
41
|
+
if component_id and component_id not in cls._hook_index:
|
|
42
|
+
cls._hook_index[component_id] = 0
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def get_hook_index(cls) -> int:
|
|
46
|
+
"""Get current hook index"""
|
|
47
|
+
if not cls._current_component:
|
|
48
|
+
cls._current_component = "default"
|
|
49
|
+
if cls._current_component not in cls._hook_index:
|
|
50
|
+
cls._hook_index[cls._current_component] = 0
|
|
51
|
+
idx = cls._hook_index[cls._current_component]
|
|
52
|
+
cls._hook_index[cls._current_component] += 1
|
|
53
|
+
return idx
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def reset_hook_index(cls):
|
|
57
|
+
"""Reset hook index for next render"""
|
|
58
|
+
if cls._current_component:
|
|
59
|
+
cls._hook_index[cls._current_component] = 0
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def get_state(cls, component_id: str) -> HookState:
|
|
63
|
+
"""Get or create state for component"""
|
|
64
|
+
if component_id not in cls._states:
|
|
65
|
+
cls._states[component_id] = HookState()
|
|
66
|
+
return cls._states[component_id]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def useState(initial_value: Any = None) -> Tuple[Any, Callable]:
|
|
70
|
+
"""
|
|
71
|
+
React-like useState hook
|
|
72
|
+
Returns (value, setValue) tuple
|
|
73
|
+
|
|
74
|
+
Usage:
|
|
75
|
+
count, set_count = useState(0)
|
|
76
|
+
set_count(count + 1)
|
|
77
|
+
"""
|
|
78
|
+
component_id = StateManager._current_component or "default"
|
|
79
|
+
hook_index = StateManager.get_hook_index()
|
|
80
|
+
state = StateManager.get_state(component_id)
|
|
81
|
+
|
|
82
|
+
state_key = f"state_{hook_index}"
|
|
83
|
+
|
|
84
|
+
if state_key not in state.values:
|
|
85
|
+
state.values[state_key] = initial_value
|
|
86
|
+
|
|
87
|
+
def setter(new_value):
|
|
88
|
+
if callable(new_value):
|
|
89
|
+
state.values[state_key] = new_value(state.values[state_key])
|
|
90
|
+
else:
|
|
91
|
+
state.values[state_key] = new_value
|
|
92
|
+
|
|
93
|
+
return state.values[state_key], setter
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def useEffect(effect: Callable, dependencies: Optional[List] = None) -> None:
|
|
97
|
+
"""
|
|
98
|
+
React-like useEffect hook
|
|
99
|
+
Runs effect function when dependencies change
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
def effect():
|
|
103
|
+
print("Component mounted or deps changed")
|
|
104
|
+
return lambda: print("Cleanup")
|
|
105
|
+
|
|
106
|
+
useEffect(effect, [count])
|
|
107
|
+
"""
|
|
108
|
+
component_id = StateManager._current_component or "default"
|
|
109
|
+
hook_index = StateManager.get_hook_index()
|
|
110
|
+
state = StateManager.get_state(component_id)
|
|
111
|
+
|
|
112
|
+
effect_key = f"effect_{hook_index}"
|
|
113
|
+
deps_key = f"deps_{hook_index}"
|
|
114
|
+
|
|
115
|
+
should_run = False
|
|
116
|
+
|
|
117
|
+
if effect_key not in state.effects:
|
|
118
|
+
should_run = True
|
|
119
|
+
elif dependencies is None:
|
|
120
|
+
should_run = True
|
|
121
|
+
elif state.effect_dependencies.get(deps_key) != dependencies:
|
|
122
|
+
should_run = True
|
|
123
|
+
|
|
124
|
+
if should_run:
|
|
125
|
+
state.effects[effect_key] = effect
|
|
126
|
+
state.effect_dependencies[deps_key] = dependencies or []
|
|
127
|
+
effect()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def useContext(context_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
React-like useContext hook
|
|
133
|
+
Provides context to component
|
|
134
|
+
|
|
135
|
+
Usage:
|
|
136
|
+
user_context = {"user": "John", "role": "admin"}
|
|
137
|
+
context = useContext(user_context)
|
|
138
|
+
"""
|
|
139
|
+
return context_dict
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def useReducer(reducer: Callable, initial_state: Any) -> Tuple[Any, Callable]:
|
|
143
|
+
"""
|
|
144
|
+
React-like useReducer hook
|
|
145
|
+
Returns (state, dispatch) tuple
|
|
146
|
+
|
|
147
|
+
Usage:
|
|
148
|
+
def reducer(state, action):
|
|
149
|
+
if action["type"] == "INCREMENT":
|
|
150
|
+
return state + 1
|
|
151
|
+
return state
|
|
152
|
+
|
|
153
|
+
count, dispatch = useReducer(reducer, 0)
|
|
154
|
+
dispatch({"type": "INCREMENT"})
|
|
155
|
+
"""
|
|
156
|
+
component_id = StateManager._current_component or "default"
|
|
157
|
+
hook_index = StateManager.get_hook_index()
|
|
158
|
+
state = StateManager.get_state(component_id)
|
|
159
|
+
|
|
160
|
+
state_key = f"reducer_{hook_index}"
|
|
161
|
+
|
|
162
|
+
if state_key not in state.values:
|
|
163
|
+
state.values[state_key] = initial_state
|
|
164
|
+
|
|
165
|
+
def dispatch(action):
|
|
166
|
+
current_state = state.values[state_key]
|
|
167
|
+
new_state = reducer(current_state, action)
|
|
168
|
+
state.values[state_key] = new_state
|
|
169
|
+
|
|
170
|
+
return state.values[state_key], dispatch
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def useCallback(callback: Callable, dependencies: Optional[List[Any]] = None) -> Callable:
|
|
174
|
+
"""
|
|
175
|
+
React-like useCallback hook
|
|
176
|
+
Memoizes callback function
|
|
177
|
+
|
|
178
|
+
Usage:
|
|
179
|
+
def on_click():
|
|
180
|
+
print("Clicked")
|
|
181
|
+
|
|
182
|
+
memoized_click = useCallback(on_click, [])
|
|
183
|
+
"""
|
|
184
|
+
component_id = StateManager._current_component or "default"
|
|
185
|
+
hook_index = StateManager.get_hook_index()
|
|
186
|
+
state = StateManager.get_state(component_id)
|
|
187
|
+
|
|
188
|
+
callback_key = f"callback_{hook_index}"
|
|
189
|
+
deps_key = f"callback_deps_{hook_index}"
|
|
190
|
+
|
|
191
|
+
if callback_key not in state.callbacks:
|
|
192
|
+
state.callbacks[callback_key] = callback
|
|
193
|
+
state.callback_dependencies[deps_key] = dependencies or []
|
|
194
|
+
elif state.callback_dependencies.get(deps_key) != dependencies:
|
|
195
|
+
state.callbacks[callback_key] = callback
|
|
196
|
+
state.callback_dependencies[deps_key] = dependencies or []
|
|
197
|
+
|
|
198
|
+
return state.callbacks[callback_key]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def useMemo(compute: Callable, dependencies: Optional[List[Any]] = None) -> Any:
|
|
202
|
+
"""
|
|
203
|
+
React-like useMemo hook
|
|
204
|
+
Memoizes expensive computations
|
|
205
|
+
|
|
206
|
+
Usage:
|
|
207
|
+
expensive_value = useMemo(lambda: compute_value(count), [count])
|
|
208
|
+
"""
|
|
209
|
+
component_id = StateManager._current_component or "default"
|
|
210
|
+
hook_index = StateManager.get_hook_index()
|
|
211
|
+
state = StateManager.get_state(component_id)
|
|
212
|
+
|
|
213
|
+
memo_key = f"memo_{hook_index}"
|
|
214
|
+
deps_key = f"memo_deps_{hook_index}"
|
|
215
|
+
|
|
216
|
+
if memo_key not in state.values:
|
|
217
|
+
state.values[memo_key] = compute()
|
|
218
|
+
state.effect_dependencies[deps_key] = dependencies or []
|
|
219
|
+
elif state.effect_dependencies.get(deps_key) != dependencies:
|
|
220
|
+
state.values[memo_key] = compute()
|
|
221
|
+
state.effect_dependencies[deps_key] = dependencies or []
|
|
222
|
+
|
|
223
|
+
return state.values[memo_key]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def useRef(initial_value: Any = None) -> Dict[str, Any]:
|
|
227
|
+
"""
|
|
228
|
+
React-like useRef hook
|
|
229
|
+
Returns a mutable ref object
|
|
230
|
+
|
|
231
|
+
Usage:
|
|
232
|
+
input_ref = useRef()
|
|
233
|
+
input_ref["current"] = some_value
|
|
234
|
+
"""
|
|
235
|
+
component_id = StateManager._current_component or "default"
|
|
236
|
+
hook_index = StateManager.get_hook_index()
|
|
237
|
+
state = StateManager.get_state(component_id)
|
|
238
|
+
|
|
239
|
+
ref_key = f"ref_{hook_index}"
|
|
240
|
+
|
|
241
|
+
if ref_key not in state.values:
|
|
242
|
+
state.values[ref_key] = {"current": initial_value}
|
|
243
|
+
|
|
244
|
+
return state.values[ref_key]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class GlobalState(Generic[T]):
|
|
248
|
+
"""Global state container for cross-component state"""
|
|
249
|
+
|
|
250
|
+
_subscribers: Dict[str, List[Callable]] = {}
|
|
251
|
+
_values: Dict[str, Any] = {}
|
|
252
|
+
|
|
253
|
+
def __init__(self, key: str, initial_value: T):
|
|
254
|
+
self.key = key
|
|
255
|
+
self._values[key] = initial_value
|
|
256
|
+
if key not in self._subscribers:
|
|
257
|
+
self._subscribers[key] = []
|
|
258
|
+
|
|
259
|
+
def get(self) -> Optional[T]:
|
|
260
|
+
"""Get current value"""
|
|
261
|
+
return self._values.get(self.key)
|
|
262
|
+
|
|
263
|
+
def set(self, value: T) -> None:
|
|
264
|
+
"""Set new value and notify subscribers"""
|
|
265
|
+
self._values[self.key] = value
|
|
266
|
+
self._notify_subscribers()
|
|
267
|
+
|
|
268
|
+
def subscribe(self, callback: Callable) -> Callable:
|
|
269
|
+
"""Subscribe to value changes"""
|
|
270
|
+
self._subscribers[self.key].append(callback)
|
|
271
|
+
|
|
272
|
+
def unsubscribe():
|
|
273
|
+
self._subscribers[self.key].remove(callback)
|
|
274
|
+
|
|
275
|
+
return unsubscribe
|
|
276
|
+
|
|
277
|
+
def _notify_subscribers(self) -> None:
|
|
278
|
+
"""Notify all subscribers of changes"""
|
|
279
|
+
for callback in self._subscribers.get(self.key, []):
|
|
280
|
+
callback(self._values[self.key])
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def create_context(default_value: Any = None) -> Dict[str, Any]:
|
|
284
|
+
"""Create a context object"""
|
|
285
|
+
return {
|
|
286
|
+
"value": default_value,
|
|
287
|
+
"consumers": []
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def useGlobalState(key: str, initial_value: Any = None) -> Tuple[Any, Callable]:
|
|
292
|
+
"""
|
|
293
|
+
Hook to use global state across components
|
|
294
|
+
|
|
295
|
+
Usage:
|
|
296
|
+
user_state = GlobalState("user", {"name": "John"})
|
|
297
|
+
user, set_user = useGlobalState("user", {"name": "John"})
|
|
298
|
+
"""
|
|
299
|
+
if key not in GlobalState._values:
|
|
300
|
+
GlobalState._values[key] = initial_value
|
|
301
|
+
GlobalState._subscribers[key] = []
|
|
302
|
+
|
|
303
|
+
def setter(value):
|
|
304
|
+
GlobalState._values[key] = value
|
|
305
|
+
for callback in GlobalState._subscribers.get(key, []):
|
|
306
|
+
callback(value)
|
|
307
|
+
|
|
308
|
+
return GlobalState._values[key], setter
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# Component decorator to set component context
|
|
312
|
+
def component(func: Callable) -> Callable:
|
|
313
|
+
"""
|
|
314
|
+
Decorator to wrap component functions with hook support
|
|
315
|
+
|
|
316
|
+
Usage:
|
|
317
|
+
@component
|
|
318
|
+
def MyComponent():
|
|
319
|
+
count, set_count = useState(0)
|
|
320
|
+
return f"Count: {count}"
|
|
321
|
+
"""
|
|
322
|
+
@wraps(func)
|
|
323
|
+
def wrapper(*args, **kwargs):
|
|
324
|
+
component_id = f"{func.__name__}_{id(func)}"
|
|
325
|
+
StateManager.set_component(component_id)
|
|
326
|
+
try:
|
|
327
|
+
result = func(*args, **kwargs)
|
|
328
|
+
finally:
|
|
329
|
+
StateManager.reset_hook_index()
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
return wrapper
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
__all__ = [
|
|
336
|
+
'useState',
|
|
337
|
+
'useEffect',
|
|
338
|
+
'useContext',
|
|
339
|
+
'useReducer',
|
|
340
|
+
'useCallback',
|
|
341
|
+
'useMemo',
|
|
342
|
+
'useRef',
|
|
343
|
+
'GlobalState',
|
|
344
|
+
'create_context',
|
|
345
|
+
'useGlobalState',
|
|
346
|
+
'component',
|
|
347
|
+
'StateManager',
|
|
348
|
+
]
|
nextpy/performance.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy Performance Optimization
|
|
3
|
+
Decorators and utilities for performance monitoring and optimization
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import functools
|
|
8
|
+
from typing import Callable, Any
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def timeit(func: Callable) -> Callable:
|
|
13
|
+
"""Decorator to measure execution time"""
|
|
14
|
+
@functools.wraps(func)
|
|
15
|
+
async def async_wrapper(*args, **kwargs):
|
|
16
|
+
start = time.time()
|
|
17
|
+
result = await func(*args, **kwargs)
|
|
18
|
+
elapsed = time.time() - start
|
|
19
|
+
print(f"{func.__name__} took {elapsed:.3f}s")
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
@functools.wraps(func)
|
|
23
|
+
def sync_wrapper(*args, **kwargs):
|
|
24
|
+
start = time.time()
|
|
25
|
+
result = func(*args, **kwargs)
|
|
26
|
+
elapsed = time.time() - start
|
|
27
|
+
print(f"{func.__name__} took {elapsed:.3f}s")
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
import inspect
|
|
31
|
+
if inspect.iscoroutinefunction(func):
|
|
32
|
+
return async_wrapper
|
|
33
|
+
return sync_wrapper
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def batch_processor(batch_size: int = 100):
|
|
37
|
+
"""Process items in batches for performance"""
|
|
38
|
+
def decorator(func: Callable) -> Callable:
|
|
39
|
+
@functools.wraps(func)
|
|
40
|
+
async def wrapper(items: list) -> list:
|
|
41
|
+
results = []
|
|
42
|
+
for i in range(0, len(items), batch_size):
|
|
43
|
+
batch = items[i:i + batch_size]
|
|
44
|
+
batch_results = await asyncio.gather(*[func(item) for item in batch])
|
|
45
|
+
results.extend(batch_results)
|
|
46
|
+
return results
|
|
47
|
+
return wrapper
|
|
48
|
+
return decorator
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RateLimiter:
|
|
52
|
+
"""Simple rate limiter"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, max_requests: int = 100, window_seconds: int = 60):
|
|
55
|
+
self.max_requests = max_requests
|
|
56
|
+
self.window_seconds = window_seconds
|
|
57
|
+
self.requests: Dict[str, list] = {}
|
|
58
|
+
|
|
59
|
+
def is_allowed(self, key: str) -> bool:
|
|
60
|
+
"""Check if request is allowed"""
|
|
61
|
+
now = time.time()
|
|
62
|
+
cutoff = now - self.window_seconds
|
|
63
|
+
|
|
64
|
+
if key not in self.requests:
|
|
65
|
+
self.requests[key] = []
|
|
66
|
+
|
|
67
|
+
# Remove old requests
|
|
68
|
+
self.requests[key] = [req_time for req_time in self.requests[key] if req_time > cutoff]
|
|
69
|
+
|
|
70
|
+
if len(self.requests[key]) < self.max_requests:
|
|
71
|
+
self.requests[key].append(now)
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Global rate limiter
|
|
78
|
+
rate_limiter = RateLimiter()
|
nextpy/plugins.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy Plugin System - Extend framework functionality
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable, Dict, Any, List
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Plugin(ABC):
|
|
10
|
+
"""Base plugin class"""
|
|
11
|
+
|
|
12
|
+
name: str = "Plugin"
|
|
13
|
+
version: str = "1.0.0"
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def on_init(self, app):
|
|
17
|
+
"""Called when app initializes"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def on_request(self, request):
|
|
22
|
+
"""Called on each request"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def on_response(self, response):
|
|
27
|
+
"""Called on each response"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PluginManager:
|
|
32
|
+
"""Manage plugins"""
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self.plugins: List[Plugin] = []
|
|
36
|
+
self.hooks: Dict[str, List[Callable]] = {}
|
|
37
|
+
|
|
38
|
+
def register(self, plugin: Plugin):
|
|
39
|
+
"""Register a plugin"""
|
|
40
|
+
self.plugins.append(plugin)
|
|
41
|
+
|
|
42
|
+
def register_hook(self, name: str, callback: Callable):
|
|
43
|
+
"""Register a hook"""
|
|
44
|
+
if name not in self.hooks:
|
|
45
|
+
self.hooks[name] = []
|
|
46
|
+
self.hooks[name].append(callback)
|
|
47
|
+
|
|
48
|
+
async def trigger(self, name: str, *args, **kwargs):
|
|
49
|
+
"""Trigger a hook"""
|
|
50
|
+
if name in self.hooks:
|
|
51
|
+
for callback in self.hooks[name]:
|
|
52
|
+
await callback(*args, **kwargs)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Global plugin manager
|
|
56
|
+
_plugin_manager = PluginManager()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_plugin_manager() -> PluginManager:
|
|
60
|
+
"""Get global plugin manager"""
|
|
61
|
+
return _plugin_manager
|
nextpy/py.typed
ADDED
|
File without changes
|