"""Scene I/O: round-trip parsing and editing of scene `.py` files.
Tier 1 (`source`) is the seam the round-trip reads and edits source through: a
document of statements, trivia and arguments, backed by a parser the tier above
never sees. Tier 2 (`scene_file`, `scene_module`) is the scene the editor holds.
Tier 3 (`emitter`, `detection`) provides greenfield Python emission for live
node trees and lightweight scene-file detection.
Every name resolves lazily (PEP 562). This package sits on the eager
``import simvx.core`` chain (``hot_reload`` imports ``loader``), and the
editing surface is far more than the browser runtime needs to run a scene: an
eager import here would make every consumer pay for it. Loading a scene *file*
costs the loader alone; the editing surface pays its import cost on first use.
"""
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
# Restated for type checkers, which cannot follow the ``__getattr__``
# below and otherwise see every one of these names as ``Any``. Nothing
# here runs, so the import chain stays exactly as cheap as before.
from . import source # noqa: F401
from .detection import ( # noqa: F401
AmbiguousSceneError,
has_procedural_construction,
is_scene_path,
primary_node_class_from_source,
)
from .emitter import ( # noqa: F401
UnemittableValueError,
emit_node_construction,
emit_scene,
emit_value,
engine_computed,
expression_describes,
expression_is_opaque,
expression_reads_a_shape,
helper_import_module,
import_module_for_type,
iter_runtime_kwargs,
structural_type_name,
)
from .loader import ( # noqa: F401
load_scene,
)
from .scene_file import ( # noqa: F401
ImportSet,
SceneClass,
SceneFile,
)
from .scene_module import ( # noqa: F401
NotASceneModuleError,
SceneModule,
)
from .symbols import ( # noqa: F401
ClassDefRef,
UseSiteRef,
find_class_definitions,
find_class_uses,
rename_class_in_source,
rename_module_in_imports,
)
from .syntax import ( # noqa: F401
SyntaxIssue,
UnsupportedSceneSyntaxError,
)
# name -> submodule that defines it. "source" maps to itself: the submodule is
# the exported object. Importing it costs nothing -- it resolves its backend on
# the first parse, not on import -- so it keeps the cheap-import property this
# whole module exists for.
_EXPORTS = {
"source": "source",
"AmbiguousSceneError": "detection",
"has_procedural_construction": "detection",
"is_scene_path": "detection",
"primary_node_class_from_source": "detection",
"UnemittableValueError": "emitter",
"emit_node_construction": "emitter",
"emit_scene": "emitter",
"emit_value": "emitter",
"engine_computed": "emitter",
"expression_describes": "emitter",
"expression_is_opaque": "emitter",
"expression_reads_a_shape": "emitter",
"helper_import_module": "emitter",
"import_module_for_type": "emitter",
"iter_runtime_kwargs": "emitter",
"structural_type_name": "emitter",
"load_scene": "loader",
"ImportSet": "scene_file",
"SceneClass": "scene_file",
"SceneFile": "scene_file",
"NotASceneModuleError": "scene_module",
"SceneModule": "scene_module",
"ClassDefRef": "symbols",
"UseSiteRef": "symbols",
"find_class_definitions": "symbols",
"find_class_uses": "symbols",
"rename_class_in_source": "symbols",
"rename_module_in_imports": "symbols",
"SyntaxIssue": "syntax",
"UnsupportedSceneSyntaxError": "syntax",
}
__all__ = sorted(_EXPORTS)
if not TYPE_CHECKING:
# Hidden from type checkers on purpose. A module-level ``__getattr__`` makes
# a checker accept ANY attribute name, which would leave the block above
# decorative: a name dropped from ``_EXPORTS``, or a typo at a call site,
# would still check clean. With it hidden, the restated names are the whole
# published surface and anything else is an error.
def __getattr__(name: str) -> Any:
module_name = _EXPORTS.get(name)
if module_name is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
from importlib import import_module
module = import_module(f".{module_name}", __name__)
value = module if name == module_name else getattr(module, name)
globals()[name] = value
return value
[docs]
def __dir__() -> list[str]:
return sorted(set(globals()) | set(__all__))