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)