""" Djarea Auto-Discovery Scans Django apps for server functions following the 'clients' layer convention: - /clients.py - /clients/**/*.py Usage in urls.py: from djarea.setup.discovery import djarea_clients djarea_clients('apps') # Scans apps/*/clients.py djarea_clients('djarea', 'allauth') # Scans djarea/allauth/**/*.py This replaces manual "import to register" patterns with explicit auto-discovery. """ from typing import Any from djarea._vendor.app_visitor import DjangoAppVisitor, get_members from .registry import register, get_function from djarea.client.function import ServerFunction class _RegisterServerFunctions: """Visitor handler that registers ServerFunction subclasses.""" def on_module( self, app_name: str, path_parts: list[str], members: list[tuple[str, Any]] ) -> None: """Process discovered module members.""" for name, member in members: # Register ServerFunction subclasses if ( isinstance(member, type) and issubclass(member, ServerFunction) and member is not ServerFunction and hasattr(member, '__name__') ): # Use the function name as registration name fn_name = getattr(member, 'name', None) or member.__name__ # Skip already registered (idempotent) if get_function(fn_name) is member: continue try: register(member, fn_name) except ValueError: # Already registered with different class - skip pass def djarea_clients(apps_root: str, layer: str = 'clients') -> None: """ Discover and register server functions from Django apps. Scans for the specified layer (default: 'clients') in each app: - /.py - //**/*.py Args: apps_root: Root package containing Django apps (e.g., 'apps') layer: Module name pattern to scan (default: 'clients') Example: # In urls.py djarea_clients('apps') # Scans apps/*/clients.py djarea_clients('apps', 'functions') # Scans apps/*/functions.py """ visitor = DjangoAppVisitor(layer=layer, apps_root=apps_root) visitor.visit(_RegisterServerFunctions()) def djarea_module(module_path: str) -> None: """ Register server functions from a specific module. Use this for library modules that don't follow the app convention. Args: module_path: Full module path (e.g., 'djarea.integrations.allauth') Example: djarea_module('djarea.integrations.allauth') djarea_module('djarea.jwt.functions') """ members = get_members(module_path) handler = _RegisterServerFunctions() handler.on_module('', [], members)