Metadata
☍
A collection of utility functions related to file, package, and program metadata.
This class relies heavily on the sys
, pathlib
, and importlib
modules, all of which are part of the Python Standard Library. Links to
specific sections in the documentation for those modules (and other useful
resources) are provided where relevant in each function description.
A collection of utility functions related to file, package, and program metadata.
This class relies heavily on the sys
, pathlib
, and importlib
modules, all of which are part of the Python Standard Library. Links to
specific sections in the documentation for those modules (and other useful
resources) are provided where relevant in each function description.
Source code in botstrap/internal/metadata.py
class Metadata:
"""A collection of utility functions related to file, package, and program metadata.
This class relies heavily on the [`sys`][1], [`pathlib`][2], and [`importlib`][3]
modules, all of which are part of the [Python Standard Library][4]. Links to
specific sections in the documentation for those modules (and other useful
resources) are provided where relevant in each function description.
[1]: https://docs.python.org/3/library/sys.html
[2]: https://docs.python.org/3/library/pathlib.html
[3]: https://docs.python.org/3/library/importlib.html
[4]: https://docs.python.org/3/library/index.html
"""
class BotClassInfo(NamedTuple):
"""A `NamedTuple` containing information for an available Discord bot class."""
qualified_name: str
"""The fully-qualified name of an available class representing a Discord bot."""
run_method_name: str = "run"
"""The name of the method to run the bot. May accept a bot token parameter."""
init_with_token: bool = False
"""Whether to pass the token into the constructor instead of the run method."""
@classmethod
def get_bot_class_info(cls) -> BotClassInfo:
"""Returns info about a Discord bot class that may be imported and instantiated.
The return value of this function is a subclass of `NamedTuple` that will
contain information about a bot class from **one** of the Python Discord
libraries for which Botstrap includes built-in support.
See [`get_discord_libs()`][botstrap.internal.Metadata.get_discord_libs]
for a list of supported libraries.
If multiple supported libraries are installed, then one of them will be chosen
arbitrarily. If **none** of the supported libraries are installed, this function
will raise a `RuntimeError`.
??? info "Info - Contents of the resulting tuple"
This function's return type, `BotClassInfo`, is fundamentally just a `tuple`
with three named fields:
- `qualified_name: str` <br>
The fully-qualified name of an available class representing a Discord bot.
- `run_method_name: str` <br>
The name of the method to run the bot. May accept a bot token parameter.
Defaults to `"run"`.
- `init_with_token: bool` <br>
Whether to pass the token into the constructor instead of the run method.
Defaults to `False`.
Returns:
A `NamedTuple` containing information about the bot class to instantiate.
Raises:
RuntimeError: If none of the Python Discord libraries with built-in support
are installed and/or recognized.
"""
try:
return cls.BotClassInfo(
*{ # type: ignore[arg-type]
"discord.py": ("discord.Client",),
"py-cord": ("discord.Bot",),
"disnake": ("disnake.ext.commands.InteractionBot",),
"hikari": ("hikari.GatewayBot", "run", True),
"discord-py-interactions": ("interactions.Client", "start", True),
"naff": ("naff.Client", "start"),
"nextcord": ("nextcord.ext.commands.Bot",),
}[cls.get_discord_libs()[0]]
)
except (IndexError, KeyError):
raise RuntimeError(
"Cannot automatically determine the class to use for the Discord bot."
) from None
@classmethod
def get_default_keys_dir(cls) -> Path:
"""Returns the path of the default key storage directory for the current script.
By default, Botstrap [`.key`](../secret#key-files) files are stored in a
directory named `.botstrap_keys`. This directory is usually placed in the
same location as the file containing the
[`"__main__"`][botstrap.internal.Metadata.get_main_file_path] module for the
executing script. If the main module cannot be found, `.botstrap_keys` will
be located in the current working directory.
The path returned by this function is **not** guaranteed to point to an
already-existing directory.
[1]: https://docs.python.org/3/library/pathlib.html#concrete-paths
Returns:
The `Path` of the default key storage directory for the current script.
"""
main_file_path = cls.get_main_file_path()
parent_dir_path = main_file_path.parent if main_file_path else _CURRENT_DIR
return parent_dir_path / ".botstrap_keys"
@classmethod
def get_discord_libs(cls) -> list[str]:
"""Returns a list of package names of all installed and supported Discord libs.
Currently, Botstrap provides built-in support for [discord.py][1],
[disnake][2], [hikari][3], [interactions.py][4],
[NAFF][5], [Nextcord][6], and  [Pycord][7]. Adding support
for more libraries is fairly straightforward, and [contributions][8] that do so
are always welcome.
[1]: https://github.com/Rapptz/discord.py
[2]: https://github.com/DisnakeDev/disnake
[3]: https://github.com/hikari-py/hikari
[4]: https://github.com/interactions-py/library
[5]: https://github.com/NAFTeam/NAFF
[6]: https://github.com/nextcord/nextcord
[7]: https://github.com/Pycord-Development/pycord
[8]: https://github.com/nuztalgia/botstrap/blob/main/.github/CONTRIBUTING.md
??? info "Info - Contents of the resulting list"
The `list` returned by this function may contain the following `str` values:
```{.py title="" .line-numbers-off}
[
"discord.py",
"disnake",
"hikari",
"discord-py-interactions",
"naff",
"nextcord",
"py-cord",
]
```
Hopefully you don't have *all* of those libraries installed simultaneously,
so your resulting `list` should be much smaller (and possibly empty,
although this is obviously not ideal) in practice.
Returns:
A list of strings corresponding to the names of installed Discord libraries.
"""
return [
lib_name
for p, package_library_names in packages_distributions().items()
if p in ("discord", "disnake", "hikari", "interactions", "naff", "nextcord")
# Pycord is supported too - it's included under the "discord" namespace.
for lib_name in package_library_names
if not ((p == "discord") and (lib_name == "nextcord")) # False positive.
]
@classmethod
def get_main_file_path(cls) -> Path | None:
"""Returns the path of the file containing the main module, if it can be found.
The **main module** (a.k.a. [`"__main__"`][1]) is essentially the top-level
environment of the currently executing script. In most applications, it can be
accessed through `sys.modules["__main__"]`, and therefore this function is
able to return a valid path most of the time.
However, in niche cases (such as when a "script" is run using Python's [`-c`][2]
command-line option), this function will be unable to find the main module and
will therefore return `None`.
[1]: https://docs.python.org/3/library/__main__.html#module-__main__
[2]: https://docs.python.org/3/using/cmdline.html#cmdoption-c
Returns:
The `Path` of the `"__main__"` module if it can be found, otherwise `None`.
"""
main_file = getattr(_MAIN_MODULE, "__file__", "") or (sys.argv and sys.argv[0])
if main_file and (main_path := Path(main_file).resolve()).exists():
return main_path
else:
return None
@classmethod
def get_package_info(cls, package_name: str = "") -> dict[str, str | list[str]]:
"""Returns a dictionary containing any available metadata about the package.
This function uses the `metadata()` function from [`importlib.metadata`][1]
to retrieve information about the specified package. If successful, it will
return a dictionary in which the keys are strings corresponding to the fields
defined by Python's [core metadata][2]. As detailed in that specification, each
value (if present) will either be a `str` or a `list[str]`.
If `package_name` belongs to a package that cannot be found or whose metadata
is otherwise unavailable, this function will simply return an empty `dict`.
[1]: https://docs.python.org/3/library/importlib.metadata.html
[2]: https://packaging.python.org/en/latest/specifications/core-metadata/
Args:
package_name:
The name of the package to fetch metadata for.
Returns:
A dictionary containing the available metadata for the specified package.
"""
if (not package_name) and not (package_name := _MAIN_MODULE.__package__ or ""):
package_name = vars(_MAIN_MODULE).get("__requires__", "")
try:
return (package_name and metadata(package_name).json) or {}
except (MessageError, PackageNotFoundError):
return {}
@classmethod
def get_program_command(cls, program_name: str) -> list[str]:
"""Returns a list of strings mirroring a command for running the current script.
If the given program name matches the name of a console script returned
by [`entry_points()`][1], this function will simply return a single-item
`list` consisting of the `program_name` string.
Otherwise, this function will iterate through [`sys.orig_argv`][2] in order
to approximate a "minimum viable command" for running the current script.
The resulting `list` will include everything from the name of the
[`sys.executable`][3] up to (and including) the first string that is either
**a)** the name of an existing file, or **b)** a non-optional argument.
[1]: https://docs.python.org/3/library/importlib.metadata.html#entry-points
[2]: https://docs.python.org/3/library/sys.html#sys.orig_argv
[3]: https://docs.python.org/3/library/sys.html#sys.executable
Args:
program_name:
The name of the currently executing program. This may be user-provided,
and thus does not necessarily match any of the strings actually used in
the command to run the script.
Returns:
A `list` where each `str` is part of the command to run the current script.
"""
if program_name in entry_points(group="console_scripts").names:
return [program_name]
def get_top_level_args() -> Iterator[str]:
"""Yields strings from `orig_argv` up to/including a non-option arg/file."""
for arg in sys.orig_argv:
arg_as_path = Path(arg)
if arg == sys.executable: # The Python executable (e.g. "python").
yield arg_as_path.stem
elif arg_as_path.exists():
try:
yield str(arg_as_path.relative_to("."))
except ValueError:
yield arg_as_path.name
return # Stop iteration upon encountering the name of a valid file.
else:
yield arg
if not arg.startswith("-"):
return # Stop iteration upon encountering a "non-option" arg.
return list(get_top_level_args())
@classmethod
def guess_program_name(cls) -> str | None:
"""Returns a possible name for the current program/script, if one can be found.
When the name of the current program needs to be displayed but its owner hasn't
explicitly specified that name, this function may be used to obtain an "educated
guess". :disguised_face:
The first source of a possible name is
[`get_package_info()`][botstrap.internal.Metadata.get_package_info], called
without a `package_name` (which doesn't necessarily result in well-defined
behavior). Failing that, this function will try to pick a relevant name out of
the path of the [`"__main__"`][botstrap.internal.Metadata.get_main_file_path]
module (if it's available) or the current working directory. If neither source
yields a viable name, this function will return `None`.
Returns:
A name for the program if a reasonable guess can be made, otherwise `None`.
"""
package_name = cls.get_package_info().get("name")
if isinstance(package_name, str):
return package_name
def is_relevant_name(path_name: str) -> bool:
"""Returns `True` if no "indicators of irrelevance" appear in the path."""
return not any(name in path_name.lower() for name in ("main", "src"))
dirs_to_climb = 2 # Climbing too far up will also yield irrelevant names.
relevant_path = cls.get_main_file_path() or _CURRENT_DIR
for path in [relevant_path, *relevant_path.parents[:dirs_to_climb]]:
if path.exists() and is_relevant_name(path.name):
return path.resolve().name
return None
@classmethod
def import_class(cls, qualified_class_name: str) -> type:
"""Returns the class if it can be imported. If unsuccessful, raises an error.
This function uses [`import_module()`][1] to dynamically import the specified
class. Note that the class name **must be fully-qualified** (i.e. include its
module name, *à la* `module_name.ClassName`) in order for the import to be
successful. If for any reason the import is unsuccessful, this function will
raise an `ImportError` or a subclass thereof.
[1]: https://docs.python.org/3/library/importlib.html#importlib.import_module
Args:
qualified_class_name:
The fully-qualified name of the class to import.
Returns:
The `type` of the specified class, if it was imported successfully.
Raises:
ImportError: If the class cannot be imported successfully in the current
environment. This may be caused by missing dependencies and/or a mistake
in the provided `qualified_class_name`.
"""
module_name, _, class_name = qualified_class_name.rpartition(".")
result = getattr(import_module(module_name), class_name, None)
if isinstance(result, type):
return result
else:
raise ImportError(f"Failed to import '{qualified_class_name}'.")
get_bot_class_info() -> BotClassInfo
classmethod
☍
Returns info about a Discord bot class that may be imported and instantiated.
The return value of this function is a subclass of NamedTuple
that will
contain information about a bot class from one of the Python Discord
libraries for which Botstrap includes built-in support.
See get_discord_libs()
for a list of supported libraries.
If multiple supported libraries are installed, then one of them will be chosen
arbitrarily. If none of the supported libraries are installed, this function
will raise a RuntimeError
.
Info - Contents of the resulting tuple
This function's return type, BotClassInfo
, is fundamentally just a tuple
with three named fields:
qualified_name: str
The fully-qualified name of an available class representing a Discord bot.run_method_name: str
The name of the method to run the bot. May accept a bot token parameter. Defaults to"run"
.init_with_token: bool
Whether to pass the token into the constructor instead of the run method. Defaults toFalse
.
Returns:
Type | Description |
---|---|
BotClassInfo
|
A |
Raises:
Type | Description |
---|---|
RuntimeError
|
If none of the Python Discord libraries with built-in support are installed and/or recognized. |
Source code in botstrap/internal/metadata.py
@classmethod
def get_bot_class_info(cls) -> BotClassInfo:
"""Returns info about a Discord bot class that may be imported and instantiated.
The return value of this function is a subclass of `NamedTuple` that will
contain information about a bot class from **one** of the Python Discord
libraries for which Botstrap includes built-in support.
See [`get_discord_libs()`][botstrap.internal.Metadata.get_discord_libs]
for a list of supported libraries.
If multiple supported libraries are installed, then one of them will be chosen
arbitrarily. If **none** of the supported libraries are installed, this function
will raise a `RuntimeError`.
??? info "Info - Contents of the resulting tuple"
This function's return type, `BotClassInfo`, is fundamentally just a `tuple`
with three named fields:
- `qualified_name: str` <br>
The fully-qualified name of an available class representing a Discord bot.
- `run_method_name: str` <br>
The name of the method to run the bot. May accept a bot token parameter.
Defaults to `"run"`.
- `init_with_token: bool` <br>
Whether to pass the token into the constructor instead of the run method.
Defaults to `False`.
Returns:
A `NamedTuple` containing information about the bot class to instantiate.
Raises:
RuntimeError: If none of the Python Discord libraries with built-in support
are installed and/or recognized.
"""
try:
return cls.BotClassInfo(
*{ # type: ignore[arg-type]
"discord.py": ("discord.Client",),
"py-cord": ("discord.Bot",),
"disnake": ("disnake.ext.commands.InteractionBot",),
"hikari": ("hikari.GatewayBot", "run", True),
"discord-py-interactions": ("interactions.Client", "start", True),
"naff": ("naff.Client", "start"),
"nextcord": ("nextcord.ext.commands.Bot",),
}[cls.get_discord_libs()[0]]
)
except (IndexError, KeyError):
raise RuntimeError(
"Cannot automatically determine the class to use for the Discord bot."
) from None
get_default_keys_dir() -> Path
classmethod
☍
Returns the path of the default key storage directory for the current script.
By default, Botstrap .key
files are stored in a
directory named .botstrap_keys
. This directory is usually placed in the
same location as the file containing the
"__main__"
module for the
executing script. If the main module cannot be found, .botstrap_keys
will
be located in the current working directory.
The path returned by this function is not guaranteed to point to an already-existing directory.
Returns:
Type | Description |
---|---|
Path
|
The |
Source code in botstrap/internal/metadata.py
@classmethod
def get_default_keys_dir(cls) -> Path:
"""Returns the path of the default key storage directory for the current script.
By default, Botstrap [`.key`](../secret#key-files) files are stored in a
directory named `.botstrap_keys`. This directory is usually placed in the
same location as the file containing the
[`"__main__"`][botstrap.internal.Metadata.get_main_file_path] module for the
executing script. If the main module cannot be found, `.botstrap_keys` will
be located in the current working directory.
The path returned by this function is **not** guaranteed to point to an
already-existing directory.
[1]: https://docs.python.org/3/library/pathlib.html#concrete-paths
Returns:
The `Path` of the default key storage directory for the current script.
"""
main_file_path = cls.get_main_file_path()
parent_dir_path = main_file_path.parent if main_file_path else _CURRENT_DIR
return parent_dir_path / ".botstrap_keys"
get_discord_libs() -> list[str]
classmethod
☍
Returns a list of package names of all installed and supported Discord libs.
Currently, Botstrap provides built-in support for discord.py, disnake, hikari, interactions.py, NAFF, Nextcord, and Pycord. Adding support for more libraries is fairly straightforward, and contributions that do so are always welcome.
Info - Contents of the resulting list
The list
returned by this function may contain the following str
values:
[
"discord.py",
"disnake",
"hikari",
"discord-py-interactions",
"naff",
"nextcord",
"py-cord",
]
Hopefully you don't have all of those libraries installed simultaneously,
so your resulting list
should be much smaller (and possibly empty,
although this is obviously not ideal) in practice.
Returns:
Type | Description |
---|---|
list[str]
|
A list of strings corresponding to the names of installed Discord libraries. |
Source code in botstrap/internal/metadata.py
@classmethod
def get_discord_libs(cls) -> list[str]:
"""Returns a list of package names of all installed and supported Discord libs.
Currently, Botstrap provides built-in support for [discord.py][1],
[disnake][2], [hikari][3], [interactions.py][4],
[NAFF][5], [Nextcord][6], and  [Pycord][7]. Adding support
for more libraries is fairly straightforward, and [contributions][8] that do so
are always welcome.
[1]: https://github.com/Rapptz/discord.py
[2]: https://github.com/DisnakeDev/disnake
[3]: https://github.com/hikari-py/hikari
[4]: https://github.com/interactions-py/library
[5]: https://github.com/NAFTeam/NAFF
[6]: https://github.com/nextcord/nextcord
[7]: https://github.com/Pycord-Development/pycord
[8]: https://github.com/nuztalgia/botstrap/blob/main/.github/CONTRIBUTING.md
??? info "Info - Contents of the resulting list"
The `list` returned by this function may contain the following `str` values:
```{.py title="" .line-numbers-off}
[
"discord.py",
"disnake",
"hikari",
"discord-py-interactions",
"naff",
"nextcord",
"py-cord",
]
```
Hopefully you don't have *all* of those libraries installed simultaneously,
so your resulting `list` should be much smaller (and possibly empty,
although this is obviously not ideal) in practice.
Returns:
A list of strings corresponding to the names of installed Discord libraries.
"""
return [
lib_name
for p, package_library_names in packages_distributions().items()
if p in ("discord", "disnake", "hikari", "interactions", "naff", "nextcord")
# Pycord is supported too - it's included under the "discord" namespace.
for lib_name in package_library_names
if not ((p == "discord") and (lib_name == "nextcord")) # False positive.
]
get_main_file_path() -> Path | None
classmethod
☍
Returns the path of the file containing the main module, if it can be found.
The main module (a.k.a. "__main__"
) is essentially the top-level
environment of the currently executing script. In most applications, it can be
accessed through sys.modules["__main__"]
, and therefore this function is
able to return a valid path most of the time.
However, in niche cases (such as when a "script" is run using Python's -c
command-line option), this function will be unable to find the main module and
will therefore return None
.
Returns:
Type | Description |
---|---|
Path | None
|
The |
Source code in botstrap/internal/metadata.py
@classmethod
def get_main_file_path(cls) -> Path | None:
"""Returns the path of the file containing the main module, if it can be found.
The **main module** (a.k.a. [`"__main__"`][1]) is essentially the top-level
environment of the currently executing script. In most applications, it can be
accessed through `sys.modules["__main__"]`, and therefore this function is
able to return a valid path most of the time.
However, in niche cases (such as when a "script" is run using Python's [`-c`][2]
command-line option), this function will be unable to find the main module and
will therefore return `None`.
[1]: https://docs.python.org/3/library/__main__.html#module-__main__
[2]: https://docs.python.org/3/using/cmdline.html#cmdoption-c
Returns:
The `Path` of the `"__main__"` module if it can be found, otherwise `None`.
"""
main_file = getattr(_MAIN_MODULE, "__file__", "") or (sys.argv and sys.argv[0])
if main_file and (main_path := Path(main_file).resolve()).exists():
return main_path
else:
return None
get_package_info(package_name: str = '') -> dict[str, str | list[str]]
classmethod
☍
Returns a dictionary containing any available metadata about the package.
This function uses the metadata()
function from importlib.metadata
to retrieve information about the specified package. If successful, it will
return a dictionary in which the keys are strings corresponding to the fields
defined by Python's core metadata. As detailed in that specification, each
value (if present) will either be a str
or a list[str]
.
If package_name
belongs to a package that cannot be found or whose metadata
is otherwise unavailable, this function will simply return an empty dict
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
package_name |
str
|
The name of the package to fetch metadata for. |
''
|
Returns:
Type | Description |
---|---|
dict[str, str | list[str]]
|
A dictionary containing the available metadata for the specified package. |
Source code in botstrap/internal/metadata.py
@classmethod
def get_package_info(cls, package_name: str = "") -> dict[str, str | list[str]]:
"""Returns a dictionary containing any available metadata about the package.
This function uses the `metadata()` function from [`importlib.metadata`][1]
to retrieve information about the specified package. If successful, it will
return a dictionary in which the keys are strings corresponding to the fields
defined by Python's [core metadata][2]. As detailed in that specification, each
value (if present) will either be a `str` or a `list[str]`.
If `package_name` belongs to a package that cannot be found or whose metadata
is otherwise unavailable, this function will simply return an empty `dict`.
[1]: https://docs.python.org/3/library/importlib.metadata.html
[2]: https://packaging.python.org/en/latest/specifications/core-metadata/
Args:
package_name:
The name of the package to fetch metadata for.
Returns:
A dictionary containing the available metadata for the specified package.
"""
if (not package_name) and not (package_name := _MAIN_MODULE.__package__ or ""):
package_name = vars(_MAIN_MODULE).get("__requires__", "")
try:
return (package_name and metadata(package_name).json) or {}
except (MessageError, PackageNotFoundError):
return {}
get_program_command(program_name: str) -> list[str]
classmethod
☍
Returns a list of strings mirroring a command for running the current script.
If the given program name matches the name of a console script returned
by entry_points()
, this function will simply return a single-item
list
consisting of the program_name
string.
Otherwise, this function will iterate through sys.orig_argv
in order
to approximate a "minimum viable command" for running the current script.
The resulting list
will include everything from the name of the
sys.executable
up to (and including) the first string that is either
a) the name of an existing file, or b) a non-optional argument.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
program_name |
str
|
The name of the currently executing program. This may be user-provided, and thus does not necessarily match any of the strings actually used in the command to run the script. |
required |
Returns:
Type | Description |
---|---|
list[str]
|
A |
Source code in botstrap/internal/metadata.py
@classmethod
def get_program_command(cls, program_name: str) -> list[str]:
"""Returns a list of strings mirroring a command for running the current script.
If the given program name matches the name of a console script returned
by [`entry_points()`][1], this function will simply return a single-item
`list` consisting of the `program_name` string.
Otherwise, this function will iterate through [`sys.orig_argv`][2] in order
to approximate a "minimum viable command" for running the current script.
The resulting `list` will include everything from the name of the
[`sys.executable`][3] up to (and including) the first string that is either
**a)** the name of an existing file, or **b)** a non-optional argument.
[1]: https://docs.python.org/3/library/importlib.metadata.html#entry-points
[2]: https://docs.python.org/3/library/sys.html#sys.orig_argv
[3]: https://docs.python.org/3/library/sys.html#sys.executable
Args:
program_name:
The name of the currently executing program. This may be user-provided,
and thus does not necessarily match any of the strings actually used in
the command to run the script.
Returns:
A `list` where each `str` is part of the command to run the current script.
"""
if program_name in entry_points(group="console_scripts").names:
return [program_name]
def get_top_level_args() -> Iterator[str]:
"""Yields strings from `orig_argv` up to/including a non-option arg/file."""
for arg in sys.orig_argv:
arg_as_path = Path(arg)
if arg == sys.executable: # The Python executable (e.g. "python").
yield arg_as_path.stem
elif arg_as_path.exists():
try:
yield str(arg_as_path.relative_to("."))
except ValueError:
yield arg_as_path.name
return # Stop iteration upon encountering the name of a valid file.
else:
yield arg
if not arg.startswith("-"):
return # Stop iteration upon encountering a "non-option" arg.
return list(get_top_level_args())
guess_program_name() -> str | None
classmethod
☍
Returns a possible name for the current program/script, if one can be found.
When the name of the current program needs to be displayed but its owner hasn't
explicitly specified that name, this function may be used to obtain an "educated
guess".
The first source of a possible name is
get_package_info()
, called
without a package_name
(which doesn't necessarily result in well-defined
behavior). Failing that, this function will try to pick a relevant name out of
the path of the "__main__"
module (if it's available) or the current working directory. If neither source
yields a viable name, this function will return None
.
Returns:
Type | Description |
---|---|
str | None
|
A name for the program if a reasonable guess can be made, otherwise |
Source code in botstrap/internal/metadata.py
@classmethod
def guess_program_name(cls) -> str | None:
"""Returns a possible name for the current program/script, if one can be found.
When the name of the current program needs to be displayed but its owner hasn't
explicitly specified that name, this function may be used to obtain an "educated
guess". :disguised_face:
The first source of a possible name is
[`get_package_info()`][botstrap.internal.Metadata.get_package_info], called
without a `package_name` (which doesn't necessarily result in well-defined
behavior). Failing that, this function will try to pick a relevant name out of
the path of the [`"__main__"`][botstrap.internal.Metadata.get_main_file_path]
module (if it's available) or the current working directory. If neither source
yields a viable name, this function will return `None`.
Returns:
A name for the program if a reasonable guess can be made, otherwise `None`.
"""
package_name = cls.get_package_info().get("name")
if isinstance(package_name, str):
return package_name
def is_relevant_name(path_name: str) -> bool:
"""Returns `True` if no "indicators of irrelevance" appear in the path."""
return not any(name in path_name.lower() for name in ("main", "src"))
dirs_to_climb = 2 # Climbing too far up will also yield irrelevant names.
relevant_path = cls.get_main_file_path() or _CURRENT_DIR
for path in [relevant_path, *relevant_path.parents[:dirs_to_climb]]:
if path.exists() and is_relevant_name(path.name):
return path.resolve().name
return None
import_class(qualified_class_name: str) -> type
classmethod
☍
Returns the class if it can be imported. If unsuccessful, raises an error.
This function uses import_module()
to dynamically import the specified
class. Note that the class name must be fully-qualified (i.e. include its
module name, à la module_name.ClassName
) in order for the import to be
successful. If for any reason the import is unsuccessful, this function will
raise an ImportError
or a subclass thereof.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
qualified_class_name |
str
|
The fully-qualified name of the class to import. |
required |
Returns:
Type | Description |
---|---|
type
|
The |
Raises:
Type | Description |
---|---|
ImportError
|
If the class cannot be imported successfully in the current
environment. This may be caused by missing dependencies and/or a mistake
in the provided |
Source code in botstrap/internal/metadata.py
@classmethod
def import_class(cls, qualified_class_name: str) -> type:
"""Returns the class if it can be imported. If unsuccessful, raises an error.
This function uses [`import_module()`][1] to dynamically import the specified
class. Note that the class name **must be fully-qualified** (i.e. include its
module name, *à la* `module_name.ClassName`) in order for the import to be
successful. If for any reason the import is unsuccessful, this function will
raise an `ImportError` or a subclass thereof.
[1]: https://docs.python.org/3/library/importlib.html#importlib.import_module
Args:
qualified_class_name:
The fully-qualified name of the class to import.
Returns:
The `type` of the specified class, if it was imported successfully.
Raises:
ImportError: If the class cannot be imported successfully in the current
environment. This may be caused by missing dependencies and/or a mistake
in the provided `qualified_class_name`.
"""
module_name, _, class_name = qualified_class_name.rpartition(".")
result = getattr(import_module(module_name), class_name, None)
if isinstance(result, type):
return result
else:
raise ImportError(f"Failed to import '{qualified_class_name}'.")