Skip to content

blext.utils

blext.utils.bl_init

Startup script for Blender, which (re)installs and runs an extension locally.


blext.utils.inline_script_metadata

Defines the BLExtSpec model.

parse_inline_script_metadata

parse_inline_script_metadata(
	*,
	block_name: str = INLINE_METADATA_BLOCK_NAME,
	py_source_code: str,
) -> dict[str, Any] | None

Parse inline script metadata from Python source code.

PARAMETER DESCRIPTION
block_name

The name of the metadata block type to parse from the source code's header. For instance, setting this to script will parse blocks starting with # /// script (and ending with # ///).

TYPE: str DEFAULT: INLINE_METADATA_BLOCK_NAME

py_source_code

The Python source code to parse for inline script metadata. The script must start with a # /// TYPE metadata blocks.

TYPE: str

RETURNS DESCRIPTION
dict[str, Any] | None

A dictionary of inline script metadata, in a format equivalent to that of pyproject.toml, if such metadata could be parsed.

dict[str, Any] | None

Otherwise the return value is None.

References

PyPa on Inline Script Metadata: https://packaging.python.org/en/latest/specifications/inline-script-metadata

Source code in blext/utils/inline_script_metadata.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def parse_inline_script_metadata(
	*,
	block_name: str = INLINE_METADATA_BLOCK_NAME,
	py_source_code: str,
) -> dict[str, typ.Any] | None:
	"""Parse inline script metadata from Python source code.

	Parameters:
		block_name: The name of the metadata block type to parse from the source code's header.
			For instance, setting this to `script` will parse blocks starting with `# /// script` (and ending with `# ///`).
		py_source_code: The Python source code to parse for inline script metadata.
			The script must start with a `# /// TYPE` metadata blocks.

	Returns:
		A dictionary of inline script metadata, in a format equivalent to that of `pyproject.toml`, if such metadata could be parsed.

		Otherwise the return value is `None`.

	References:
		PyPa on Inline Script Metadata: <https://packaging.python.org/en/latest/specifications/inline-script-metadata>
	"""
	metadata_str = parse_inline_script_metadata_str(
		block_name=block_name, py_source_code=py_source_code
	)
	return tomllib.loads(metadata_str) if metadata_str is not None else None

parse_inline_script_metadata_str

parse_inline_script_metadata_str(
	*,
	block_name: str = INLINE_METADATA_BLOCK_NAME,
	py_source_code: str,
) -> str | None

Parse inline script metadata from Python source code.

PARAMETER DESCRIPTION
block_name

The name of the metadata block type to parse from the source code's header. For instance, setting this to script will parse blocks starting with # /// script (and ending with # ///).

TYPE: str DEFAULT: INLINE_METADATA_BLOCK_NAME

py_source_code

The Python source code to parse for inline script metadata. The script must start with a # /// TYPE metadata blocks.

TYPE: str

RETURNS DESCRIPTION
str | None

A string of inline script metadata, in a format equivalent to that of pyproject.toml, if such metadata could be parsed.

str | None

Otherwise the return value is None.

References

PyPa on Inline Script Metadata: https://packaging.python.org/en/latest/specifications/inline-script-metadata

Source code in blext/utils/inline_script_metadata.py
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
60
61
62
63
64
65
66
def parse_inline_script_metadata_str(
	*,
	block_name: str = INLINE_METADATA_BLOCK_NAME,
	py_source_code: str,
) -> str | None:
	"""Parse inline script metadata from Python source code.

	Parameters:
		block_name: The name of the metadata block type to parse from the source code's header.
			For instance, setting this to `script` will parse blocks starting with `# /// script` (and ending with `# ///`).
		py_source_code: The Python source code to parse for inline script metadata.
			The script must start with a `# /// TYPE` metadata blocks.

	Returns:
		A string of inline script metadata, in a format equivalent to that of `pyproject.toml`, if such metadata could be parsed.

		Otherwise the return value is `None`.

	References:
		PyPa on Inline Script Metadata: <https://packaging.python.org/en/latest/specifications/inline-script-metadata>
	"""
	matches = [
		match
		for match in re.finditer(INLINE_SCRIPT_METADATA_REGEX, py_source_code)
		if match.group('type') == block_name
	]

	if len(matches) == 1:
		return ''.join(
			line[2:] if line.startswith('# ') else line[1:]
			for line in matches[0].group('content').splitlines(keepends=True)
		)

	if len(matches) > 1:
		msg = f'Multiple `{block_name}` blocks of inline script metadata were found.'
		raise ValueError(msg)

	return None

replace_inline_script_metadata_str

replace_inline_script_metadata_str(
	*,
	block_name: str = INLINE_METADATA_BLOCK_NAME,
	py_source_code: str,
	metadata_str: str,
) -> str

Generate a string where the inline script metadata in the given source code has been replaced by an (also given) TOML string.

Source code in blext/utils/inline_script_metadata.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def replace_inline_script_metadata_str(
	*,
	block_name: str = INLINE_METADATA_BLOCK_NAME,
	py_source_code: str,
	metadata_str: str,
) -> str:
	"""Generate a string where the inline script metadata in the given source code has been replaced by an (also given) TOML string."""
	matches = [
		match
		for match in re.finditer(INLINE_SCRIPT_METADATA_REGEX, py_source_code)
		if match.group('type') == block_name
	]

	if len(matches) == 1:
		metadata_lines = metadata_str.strip().split('\n')
		return py_source_code.replace(
			matches[0].group('content').removesuffix('\n'),
			'\n'.join(['# ' + line for line in metadata_lines]),
		)

	msg = f'Only one `{block_name}` block of inline script metadata is supported.'
	raise ValueError(msg)

blext.utils.lru_method

Implements the lru_method decorator.

lru_method

lru_method(
	maxsize: int = 128, typed: bool = False
) -> Callable[
	[
		Callable[
			Concatenate[ClassType, ParamsType], ReturnType
		]
	],
	Callable[
		Concatenate[ClassType, ParamsType], ReturnType
	],
]

Memoize a method of an immutable object, such that the cache is deleted with the object.

Source code in blext/utils/lru_method.py
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
60
61
62
63
64
def lru_method(
	maxsize: int = 128,
	typed: bool = False,
) -> typ.Callable[
	[typ.Callable[typ.Concatenate[ClassType, ParamsType], ReturnType]],
	typ.Callable[typ.Concatenate[ClassType, ParamsType], ReturnType],
]:
	"""Memoize a method of an immutable object, such that the cache is deleted with the object."""

	def method_decorator(
		method: typ.Callable[typ.Concatenate[ClassType, ParamsType], ReturnType],
	) -> typ.Callable[typ.Concatenate[ClassType, ParamsType], ReturnType]:
		"""Factory-constructed decorator to apply to the class method."""

		@functools.wraps(method)
		def method_decorated(
			instance: ClassType, *args: ParamsType.args, **kwargs: ParamsType.kwargs
		) -> ReturnType:
			"""Modified class method that maintains an LRU cache."""
			weak_instance = weakref.ref(instance)

			@functools.wraps(method)
			@functools.lru_cache(maxsize=maxsize, typed=typed)
			def cached_method(*args: ParamsType.args, **kwargs: ParamsType.kwargs):
				realized_weak_instance = weak_instance()
				if realized_weak_instance:
					return method(realized_weak_instance, *args, **kwargs)

				msg = 'Tried to get value of an `@lru_method`-decorated instance method, but that instance no longer exists.'
				raise RuntimeError(msg)

			object.__setattr__(instance, method.__name__, cached_method)
			return cached_method(*args, **kwargs)

		return method_decorated

	return method_decorator

blext.utils.pretty_exceptions

Exception handling for enhancing the end-user experience of dealing with errors.

exception_hook

exception_hook(
	ex_type: type[BaseException],
	ex: BaseException,
	traceback: TracebackType | None,
) -> None

Prettifies exceptions incl. allowing the use of markdown in messages.

PARAMETER DESCRIPTION
ex_type

Type of the exception that was raised.

TYPE: type[BaseException]

ex

The exception that was raised.

TYPE: BaseException

traceback

The reported traceback.

TYPE: TracebackType | None

Source code in blext/utils/pretty_exceptions.py
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def exception_hook(
	ex_type: type[BaseException],
	ex: BaseException,
	traceback: types.TracebackType | None,
) -> None:
	"""Prettifies exceptions incl. allowing the use of markdown in messages.

	Parameters:
		ex_type: Type of the exception that was raised.
		ex: The exception that was raised.
		traceback: The reported traceback.

	"""
	ex_name = ex.__class__.__name__

	if isinstance(ex, pyd.ValidationError):
		ex_name = f'ValidationError{"s" if ex.error_count() > 1 else ""}'
		messages = [
			f'`{ex.title}`: {ex.error_count()} errors were encountered while parsing inputs.',
			*functools.reduce(
				operator.add,
				[
					[
						f'> `{ex.title}.{".".join(str(el) for el in err["loc"])}`',
						f'> - **Input**: `{err["input"]}`',
						f'> - **Error**: {err["msg"]}.',
						f'> - **Context**: `{err["ctx"]}`'  # pyright: ignore[reportTypedDictNotRequiredAccess]
						if err.get('ctx') is not None
						else '>',
						'>',
						f'> **Error Ref**: <{err.get("url")}>'
						if err.get('url') is not None
						else '>',
						'',
					]
					for err in ex.errors()
				],
			),
		]

	elif isinstance(ex, ValueError | NotImplementedError):
		messages = [str(arg) for arg in ex.args]  # pyright: ignore[reportAny]

	else:
		rich_traceback = rich.traceback.Traceback.from_exception(
			ex_type,
			ex,
			traceback,
			width=ERROR_CONSOLE.width,
			word_wrap=True,
		)
		ERROR_CONSOLE.print(rich_traceback)
		return

	md_messages = rich.markdown.Markdown('\n'.join(messages))

	# Present
	ERROR_CONSOLE.print(
		'\n',
		f'[bold red]{ex_name}[/bold red]',
		'\n',
		md_messages,
		sep='',
	)

blext.utils.pydantic_frozendict

Implements a pydantic wrapper for frozendict.frozendict.

ATTRIBUTE DESCRIPTION
FrozenDict

pydantic-compatible alias for frozendict.frozendict.


blext.utils.search_in_parents

Tools for finding common information and files using platform-specific methods.

search_in_parents

search_in_parents(path: Path, filename: str) -> Path | None

Search all parents of a path for a file.

Notes

The input path is itself searched for the filename, but only if it is a directory.

PARAMETER DESCRIPTION
path

The path to search the parents of.

TYPE: Path

RETURNS DESCRIPTION
Path | None

Absolute path to the found file, else None if no file was found.

Source code in blext/utils/search_in_parents.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def search_in_parents(path: Path, filename: str) -> Path | None:
	"""Search all parents of a path for a file.

	Notes:
		The input `path` is itself searched for the filename, but only if it is a directory.

	Parameters:
		path: The path to search the parents of.

	Returns:
		Absolute path to the found file, else `None` if no file was found.
	"""
	# No File Found
	if path == Path(path.root) or path == path.parent:
		return None

	# File Found
	if path.is_dir():
		file_path = path / filename
		if file_path.is_file():
			return file_path.resolve()

	# Recurse
	return search_in_parents(path.parent, filename)