Skip to content

Commit 45d79c8

Browse files
kesmit13claude
andcommitted
Pass plugin module directly to FunctionRegistry.initialize()
Instead of scanning all sys.modules, pass the imported plugin module directly so discovery is targeted and avoids false positives from infrastructure modules. Also hardens the sys.modules fallback path against RuntimeError (dict changed during iteration) and TypeError from problematic module attributes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bd935c4 commit 45d79c8

2 files changed

Lines changed: 34 additions & 11 deletions

File tree

singlestoredb/functions/ext/plugin/registry.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,29 @@ class FunctionRegistry:
144144
def __init__(self) -> None:
145145
self.functions: Dict[str, Dict[str, Any]] = {}
146146

147-
def initialize(self) -> None:
147+
def initialize(self, plugin_module: Any = None) -> None:
148148
"""Initialize and discover UDF functions from loaded modules.
149149
150-
Scans sys.modules for any module containing @udf-decorated
150+
If plugin_module is provided, only that module is scanned.
151+
Otherwise scans sys.modules for any module containing @udf-decorated
151152
functions. No _exports.py is needed -- modules just need to be
152153
imported before initialize() is called (componentize-py captures
153154
them at build time).
154155
"""
155-
self._discover_udf_functions()
156+
if plugin_module is not None:
157+
self._extract_functions(plugin_module)
158+
if self.functions:
159+
logger.info(
160+
f'Discovered UDF functions from module: '
161+
f'{plugin_module.__name__}',
162+
)
163+
else:
164+
logger.warning(
165+
f'No @udf functions found in module: '
166+
f'{plugin_module.__name__}',
167+
)
168+
else:
169+
self._discover_udf_functions()
156170

157171
@staticmethod
158172
def _is_stdlib_or_infra(mod_name: str, mod_file: str) -> bool:
@@ -164,8 +178,9 @@ def _is_stdlib_or_infra(mod_name: str, mod_file: str) -> bool:
164178
"""
165179
_infra = frozenset({
166180
'udf_handler',
181+
'singlestoredb',
167182
})
168-
if mod_name in _infra:
183+
if mod_name in _infra or mod_name.startswith('singlestoredb.'):
169184
return True
170185

171186
real_file = os.path.realpath(mod_file)
@@ -197,10 +212,18 @@ def _discover_udf_functions(self) -> None:
197212
if self._is_stdlib_or_infra(mod_name, mod_file):
198213
continue
199214

200-
if not any(
201-
hasattr(obj, '_singlestoredb_attrs')
202-
for obj in vars(mod).values()
203-
):
215+
def _has_udf_marker(obj: object) -> bool:
216+
try:
217+
return hasattr(obj, '_singlestoredb_attrs')
218+
except (TypeError, Exception):
219+
return False
220+
221+
try:
222+
mod_vars = list(vars(mod).values())
223+
except RuntimeError:
224+
continue
225+
226+
if not any(_has_udf_marker(obj) for obj in mod_vars):
204227
continue
205228

206229
self._extract_functions(mod)

singlestoredb/functions/ext/plugin/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,11 @@ def _initialize_registry(self) -> FunctionRegistry:
487487

488488
# Import the plugin module
489489
logger.info(f'Importing plugin module: {plugin_name}')
490-
importlib.import_module(plugin_name)
490+
mod = importlib.import_module(plugin_name)
491491

492-
# Initialize registry (discovers @udf functions from sys.modules)
492+
# Initialize registry (discovers @udf functions from the plugin module)
493493
registry = FunctionRegistry()
494-
registry.initialize()
494+
registry.initialize(plugin_module=mod)
495495

496496
func_count = len(registry.functions)
497497
if func_count == 0:

0 commit comments

Comments
 (0)