"""Object serializing for Python scripts namespaces."""
from __future__ import annotations
import re
import typing as t
from project_config.compat import TypeAlias
Namespace: TypeAlias = t.Dict[str, t.Any]
[docs]def loads(string: str, namespace: Namespace = {}) -> Namespace:
"""Execute a Python file and exposes their namespace as a dictionary.
The logic is based in Sphinx's configuration file loader:
https://github.com/sphinx-doc/sphinx/blob/4d7558e9/sphinx/config.py#L332
Args:
string (str): Python script.
namespace (dict): Namespace to update.
Returns:
dict: Global namespace of the Python script as an object.
"""
code = compile(string, "utf-8", "exec")
exec(code, namespace)
del namespace["__builtins__"] # we don't care about builtins
return namespace
[docs]def _pyobject_to_string(value: t.Any) -> str:
if isinstance(value, str):
escaped_value = re.sub(r"([\"])", r"\\\1", value)
return f'"{escaped_value}"'
if isinstance(value, bool):
return "True" if value else "False"
elif isinstance(value, type):
value = value.__name__
elif isinstance(value, dict):
if len(str(value)) > 60:
newline = "\n "
delimiter = "\n"
else:
newline = " "
delimiter = ""
result = (
"{"
+ (newline if newline != " " else "")
+ f",{newline}".join(
f'"{key}": {str(_pyobject_to_string(item))}'
for key, item in value.items()
)
)
if newline != " ":
result += ","
result += delimiter + "}"
return result
elif isinstance(value, list):
if len(str(value)) > 60:
newline = "\n "
delimiter = "\n"
else:
newline = " "
delimiter = ""
result = (
"["
+ (newline if newline != " " else "")
+ f",{newline}".join(
str(_pyobject_to_string(item)) for item in value
)
)
if newline != " ":
result += ","
result += delimiter + "]"
return result
return t.cast(str, value)
[docs]def dumps(obj: t.Any) -> str:
"""Converts a namespace to a Python script.
Args:
obj (dict): Namespace to convert.
Returns:
str: Python script.
"""
result = ""
for key, value in obj.items():
result += f"{key} = {_pyobject_to_string(value)}\n"
return result