Source code for ferrosoft.importutils

#  Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
"""
Restricted dynamic import utility for loading objects from first-party app modules.

This module provides a single entry-point, :func:`import_object`, for
dynamically importing Python objects (functions, classes, etc.) by their
fully-qualified name.  It enforces a namespace restriction so that only objects
whose module path starts with ``"ferrosoft.apps."`` can be loaded, preventing
an attacker-controlled or misconfigured string from importing arbitrary Python
code.

Typical use case: the task queue and the global updater system store
callable references as dotted strings in configuration or the database.
:func:`import_object` resolves those strings at runtime without exposing a
general-purpose code-execution surface.
"""

import importlib

#: All importable object names must start with this prefix.
#: Enforces that only first-party application modules can be loaded dynamically.
_REQUIRED_MODULE = "ferrosoft.apps."


[docs] class Error(Exception): """Base exception class for import utility errors."""
[docs] class ForbiddenModule(Error): """Raised when the requested module path fails the namespace check. This is a security guard: only modules whose fully-qualified name starts with :data:`_REQUIRED_MODULE` (``"ferrosoft.apps."``) may be imported dynamically. Any attempt to import from the standard library, third-party packages, or other internal namespaces raises this exception. Args: qualified_name: The fully-qualified object name that was rejected. """
[docs] def import_object(qualified_name: str): """Import and return a named object from a first-party application module. Splits ``qualified_name`` on the last ``"."`` to obtain the module path and the attribute name, imports the module via :func:`importlib.import_module`, and returns the named attribute. The namespace guard enforces that ``qualified_name`` starts with ``"ferrosoft.apps."`` before any import is attempted, limiting the attack surface of dynamic import to first-party application code only. Args: qualified_name: Fully-qualified name of the object to import, e.g. ``"ferrosoft.apps.emiflow.services.catalog.build_catalog"``. Must start with ``"ferrosoft.apps."``. Returns: The imported object, or ``None`` if the module does not expose an attribute with the given name. Raises: ForbiddenModule: If ``qualified_name`` does not start with ``"ferrosoft.apps."``. Example: :: from ferrosoft.importutils import import_object updater_fn = import_object( "ferrosoft.apps.ferrobase.services.update.run_global_update" ) if updater_fn is not None: updater_fn() """ # Require function name starts with this prefix, to # limit arbitrary execution of code (somewhat). if not qualified_name.startswith(_REQUIRED_MODULE): raise ForbiddenModule(qualified_name) module_name, func_name = qualified_name.rsplit(".", 1) module = importlib.import_module(module_name) return getattr(module, func_name, None)