Skip to content

confingy.utils.imports#

imports #

Import utility functions for confingy.

get_module_name #

get_module_name(obj: Any) -> str

Get the actual module name for an object, resolving main to the real module name.

When a Python script is run directly (e.g., python script.py), classes and functions defined in that script have module == 'main'. This function attempts to resolve the actual module name by inspecting the file path.

Source code in src/confingy/utils/imports.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def get_module_name(obj: Any) -> str:
    """Get the actual module name for an object, resolving __main__ to the real module name.

    When a Python script is run directly (e.g., `python script.py`), classes and functions
    defined in that script have __module__ == '__main__'. This function attempts to resolve
    the actual module name by inspecting the file path.
    """
    module_name = getattr(obj, "__module__", None)

    if module_name is not None and module_name != "__main__":
        return module_name

    # Try to get the file where the object was defined
    try:
        # For classes and functions, use them directly; for instances, use their class
        if inspect.isclass(obj) or inspect.isfunction(obj) or inspect.ismethod(obj):
            source_file = inspect.getfile(obj)
        else:
            source_file = inspect.getfile(obj.__class__)
    except (TypeError, OSError):
        # If we can't get the file, fall back to __main__
        return "__main__"

    # Try to convert the file path to a module name
    source_path = Path(source_file).resolve()

    # Look for the file in sys.path to determine the module name
    for path_str in sys.path:
        if not path_str:
            continue
        path = Path(path_str).resolve()
        try:
            relative = source_path.relative_to(path)
            # Convert path to module name (remove .py and replace / with .)
            if relative.suffix == ".py":
                module_parts = relative.with_suffix("").parts
                # Skip if it would result in an invalid module name
                if all(part.isidentifier() for part in module_parts):
                    return ".".join(module_parts)
        except ValueError:
            # source_path is not relative to this path
            continue

    # If we couldn't determine the module, try using just the filename without extension
    stem = source_path.stem
    if stem.isidentifier() and stem != "__main__":
        return stem

    # Fall back to __main__ if we can't determine the actual module
    return "__main__"

derive_module_name #

derive_module_name(file_path: Path) -> str

Derive the module name from a file path relative to the current working directory.

The current working directory is assumed to be the root of the module structure. For example, if cwd is /root/project and file is /root/project/foo/bar/baz.py, the module name will be foo.bar.baz.

Parameters:

Name Type Description Default
file_path Path

Path to the Python file

required

Returns:

Type Description
str

The derived module name (e.g., "training.fingys.apollo.my_module")

Source code in src/confingy/utils/imports.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def derive_module_name(file_path: Path) -> str:
    """
    Derive the module name from a file path relative to the current working directory.

    The current working directory is assumed to be the root of the module structure.
    For example, if cwd is /root/project and file is /root/project/foo/bar/baz.py,
    the module name will be foo.bar.baz.

    Args:
        file_path: Path to the Python file

    Returns:
        The derived module name (e.g., "training.fingys.apollo.my_module")
    """
    file_path = file_path.resolve()
    cwd = Path.cwd().resolve()

    try:
        # Try to make the file path relative to the current working directory
        relative_path = file_path.relative_to(cwd)
    except ValueError:
        # File is outside the current working directory, just use the filename
        return file_path.stem

    # Convert path to module name: remove .py extension and replace / with .
    parts = relative_path.with_suffix("").parts
    if len(parts) > 1 and parts[-1] == "__init__":
        parts = parts[:-1]
    return ".".join(parts)

load_variable_from_file #

load_variable_from_file(
    file_spec: str, default_name: str = "config"
) -> Any

Load a variable from a Python file or dotted module path.

Parameters:

Name Type Description Default
file_spec str

File path or dotted module path with optional variable name. Supported formats: - "path/to/file.py" (file path) - "path/to/file.py::variable_name" (file path with variable) - "path.to.module" (dotted module path) - "path.to.module::variable_name" (dotted module path with variable) If variable name is not specified, defaults to default_name.

required
default_name str

Default variable name to use if not specified in file_spec

'config'

Returns:

Type Description
Any

The variable loaded from the file

Raises:

Type Description
ValueError

If the file_spec format is invalid or variable is not found

FileNotFoundError

If the file doesn't exist

Source code in src/confingy/utils/imports.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def load_variable_from_file(file_spec: str, default_name: str = "config") -> Any:
    """
    Load a variable from a Python file or dotted module path.

    Args:
        file_spec: File path or dotted module path with optional variable name.
                  Supported formats:
                    - "path/to/file.py" (file path)
                    - "path/to/file.py::variable_name" (file path with variable)
                    - "path.to.module" (dotted module path)
                    - "path.to.module::variable_name" (dotted module path with variable)
                  If variable name is not specified, defaults to default_name.
        default_name: Default variable name to use if not specified in file_spec

    Returns:
        The variable loaded from the file

    Raises:
        ValueError: If the file_spec format is invalid or variable is not found
        FileNotFoundError: If the file doesn't exist
    """
    if "::" in file_spec:
        path_part, variable_name = file_spec.split("::", 1)
    else:
        path_part = file_spec
        variable_name = default_name

    if _is_file_path(path_part):
        file_path_obj = Path(path_part)
    else:
        file_path_obj = _resolve_module_path(path_part)

    if not file_path_obj.exists():
        raise FileNotFoundError(f"File not found: {file_path_obj}")

    # Derive the proper module name from the file path
    module_name = derive_module_name(file_path_obj)
    spec = importlib.util.spec_from_file_location(module_name, file_path_obj)
    if spec is None or spec.loader is None:
        raise ValueError(f"Could not load module from {file_path_obj}")

    module = importlib.util.module_from_spec(spec)

    # Add the file's directory to sys.path temporarily to allow relative imports
    file_dir = str(file_path_obj.parent.resolve())
    sys.path.insert(0, file_dir)
    try:
        spec.loader.exec_module(module)
    finally:
        sys.path.remove(file_dir)

    if not hasattr(module, variable_name):
        raise ValueError(
            f"Variable '{variable_name}' not found in {file_path_obj}. "
            f"Available names: {', '.join(name for name in dir(module) if not name.startswith('_'))}"
        )

    return getattr(module, variable_name)

get_class_name #

get_class_name(cls: type) -> str

Get the name to record for a class so it can be imported again.

Returns the qualified name, so classes nested in other classes (e.g. Outer.Inner) can be found. Classes defined inside functions can't be imported at all, so for those the plain __name__ is returned.

Parameters:

Name Type Description Default
cls type

The class.

required

Returns:

Type Description
str

A dotted name relative to the class's module, or the plain name.

Source code in src/confingy/utils/imports.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def get_class_name(cls: type) -> str:
    """Get the name to record for a class so it can be imported again.

    Returns the qualified name, so classes nested in other classes (e.g.
    `Outer.Inner`) can be found. Classes defined inside functions can't be
    imported at all, so for those the plain `__name__` is returned.

    Args:
        cls: The class.

    Returns:
        A dotted name relative to the class's module, or the plain name.
    """
    qualname = getattr(cls, "__qualname__", cls.__name__)
    if "<locals>" in qualname:
        return cls.__name__
    return qualname

import_qualname #

import_qualname(module_name: str, qualname: str) -> Any

Import an object by module name and dotted qualified name.

Parameters:

Name Type Description Default
module_name str

The module to import, e.g. "pkg.mod".

required
qualname str

The dotted name within the module, e.g. "Outer.Inner".

required

Returns:

Type Description
Any

The object.

Raises:

Type Description
ImportError

If the module can't be imported.

AttributeError

If the name can't be found in the module.

Source code in src/confingy/utils/imports.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def import_qualname(module_name: str, qualname: str) -> Any:
    """Import an object by module name and dotted qualified name.

    Args:
        module_name: The module to import, e.g. `"pkg.mod"`.
        qualname: The dotted name within the module, e.g. `"Outer.Inner"`.

    Returns:
        The object.

    Raises:
        ImportError: If the module can't be imported.
        AttributeError: If the name can't be found in the module.
    """
    obj: Any = importlib.import_module(module_name)
    for part in qualname.split("."):
        obj = getattr(obj, part)
    return obj