@@ -61,7 +61,7 @@ def interpolate_environment_variables(
6161@_t .overload
6262def get_by_full_name (
6363 name : str ,
64- default_module : str | _ModuleType = ...,
64+ default_module : str | _ModuleType | object = ...,
6565 * ,
6666 instance_of : _t .Literal [None ] = None ,
6767 subclass_of : _t .Literal [None ] = None ,
@@ -72,7 +72,7 @@ def get_by_full_name(
7272@_t .overload
7373def get_by_full_name (
7474 name : str ,
75- default_module : str | _ModuleType = ...,
75+ default_module : str | _ModuleType | object = ...,
7676 * ,
7777 instance_of : type [GBFNReturnType ],
7878 subclass_of : _t .Literal [None ] = None ,
@@ -83,7 +83,7 @@ def get_by_full_name(
8383@_t .overload
8484def get_by_full_name (
8585 name : str ,
86- default_module : str | _ModuleType = ...,
86+ default_module : str | _ModuleType | object = ...,
8787 * ,
8888 instance_of : _t .Literal [None ] = None ,
8989 subclass_of : type [GBFNReturnType ],
@@ -93,21 +93,26 @@ def get_by_full_name(
9393
9494def get_by_full_name (
9595 name : str ,
96- default_module : str | _ModuleType | None = None ,
96+ default_module : str | _ModuleType | object | None = None ,
9797 * ,
9898 instance_of : type [GBFNReturnType ] | None = None ,
9999 subclass_of : type [GBFNReturnType ] | None = None ,
100100) -> _t .Any :
101- """Combine :py:func:`~importlib.import_module` and :py:func:`getattr` to retrieve items by name.
101+ """Combine :py:func:`importlib.import_module` and :py:func:`getattr` to retrieve items by name.
102+
103+ You may retrieve top-level module members such as classes by adding the class name after the module path (e.g.
104+ ``logging.Logger``). The member may also be specifying using the ``':'``-syntax, e.g. ``logging:Logger``. The
105+ ``':'``-syntax also supports retrieving object members e.g. ``logging:Logger.info``. This is not possible using the
106+ dot-based syntax since ``logging.Logger`` cannot be imported by the ``importlib.import_module`` function.
102107
103108 Args:
104109 name: A name or fully qualified name.
105- default_module: A namespace to search if `name` is not fully qualified (contains no ``'. '``- characters) .
110+ default_module: A namespace to search if `name` does not contain any ``'.'`` or ``': '`` characters.
106111 instance_of: If given, perform :py:func:`isinstance` check on `name`.
107112 subclass_of: If given, perform :py:func:`issubclass` check on `name`.
108113
109114 Returns:
110- An object with the fully qualified name `name`.
115+ The object specified by `name`.
111116
112117 Raises:
113118 ValueError: If `name` does not contain any dots and ``default_module=None``.
@@ -129,12 +134,29 @@ def get_by_full_name(
129134 >>> get_by_full_name("logging.Logger", subclass_of=logging.Filterer)
130135 <class 'logging.Logger'>
131136
132- Falling back to builtins.
137+ Default namespaces may be specified using the `default_module` keyword argument.
138+
139+ >>> get_by_full_name("Logger", default_module="logging")
140+ <class 'logging.Logger'>
141+
142+ The default namespace doesn't have to be a module.
133143
134- >>> get_by_full_name("int ", default_module="builtins")
135- <class 'int'>
144+ >>> get_by_full_name("info ", default_module="logging.Logger").__qualname__
145+ 'Logger.info'
136146
147+ To retrieve an attribute of anything other than a module (e.g. a class member), you must use the ``:``-syntax to
148+ separate the module path from the attribute path.
149+
150+ >>> get_by_full_name("logging:Logger.info").__qualname__
151+ 'Logger.info'
152+
153+ When using this syntax, the path to the left of the separator must be a valid module path.
137154 """
155+ name = name .strip ()
156+ if name == "" :
157+ msg = "Name must not be empty."
158+ raise ValueError (msg )
159+
138160 if not (instance_of is None or subclass_of is None ):
139161 msg = f"At least one of ({ instance_of = } , { subclass_of = } ) must be None."
140162 raise ValueError (msg )
@@ -165,18 +187,43 @@ def get_by_full_name(
165187 return obj
166188
167189
168- def _get_by_full_name (name : str , * , default_module : str | _ModuleType | None = None ) -> _t .Any :
169- if "." in name :
190+ def _get_by_full_name (name : str , * , default_module : str | object | None = None ) -> _t .Any :
191+ path : list [str ]
192+
193+ force_default_module = name [0 ] == ":"
194+
195+ if force_default_module and not default_module :
196+ msg = f"Cannot use { name = } (with leading ':') without a default module."
197+ raise ValueError (msg )
198+
199+ root : object
200+ if ":" in name and not force_default_module :
201+ module_name , _ , member = name .rpartition (":" )
202+ root = _import_module (module_name )
203+ path = member .split ("." )
204+ elif "." in name and not force_default_module :
170205 module_name , _ , member = name .rpartition ("." )
171- module = _import_module (module_name )
206+ root = _import_module (module_name )
207+ path = [member ]
172208 else :
173209 if not default_module :
174210 msg = "Name must be fully qualified when no default module is given."
175211 raise ValueError (msg )
176- module = _import_module (default_module ) if isinstance (default_module , str ) else default_module
177- member = name
178212
179- return getattr (module , member )
213+ if isinstance (default_module , str ):
214+ if ":" in default_module or "." in default_module :
215+ root = _get_by_full_name (default_module )
216+ else :
217+ root = _import_module (default_module )
218+ else :
219+ root = default_module
220+
221+ path = name .removeprefix (":" ).split ("." )
222+
223+ obj = root
224+ for attr in path :
225+ obj = getattr (obj , attr )
226+ return obj
180227
181228
182229def get_public_module (obj : _t .Any , resolve_reexport : bool = False , include_name : bool = False ) -> str :
0 commit comments