simvx.core.scene_io.scene_file¶
High-level scene-shaped editing surface over a parsed source file.
This is Tier 3b of the scene I/O layer. It composes the statement-level
substrate (:mod:~simvx.core.scene_io.source) with greenfield emission
(:mod:emitter) and structural detection (:mod:detection) into a small
public API:
SceneFile : a parsed Python file with byte-perfect round-trip save.
SceneClass: an editable view of one Node-subclass in the file.
ImportSet : an editable view of the file's top-level imports.
The editor’s save/load path and the IDE’s refactor tools build on this surface. Lower tiers remain available for callers that need finer control.
Module Contents¶
Classes¶
What taking a child out of |
|
A parsed Python scene file with byte-perfect round-trip save. |
|
An editable view of one Node-subclass class definition inside a scene. |
|
Editable view of the file’s top-level imports. |
Data¶
API¶
- simvx.core.scene_io.scene_file.__all__¶
[‘ImportSet’, ‘Removed’, ‘SceneClass’, ‘SceneFile’]
- class simvx.core.scene_io.scene_file.Removed[source]¶
Bases:
typing.NamedTupleWhat taking a child out of
__init__carried off besides its own lines.A removal takes every statement standing on the child, and a caller with somewhere to say so should say what those were: the author wrote them and they are not coming back (:meth:
SceneClass.remove_child).- statements: list[str]¶
None
- attributes: list[str]¶
None
- class simvx.core.scene_io.scene_file.SceneFile(document: simvx.core.scene_io.source.SourceDocument, *, path: pathlib.Path | None)[source]¶
A parsed Python scene file with byte-perfect round-trip save.
Holds a source document plus the original on-disk text. All edits go through the document (:mod:
~simvx.core.scene_io.source) and :meth:savewrites what it dumps. Round-trip identity is guaranteed when no edits were made.Initialization
- __slots__¶
(‘_document’, ‘_path’, ‘_imports’, ‘_original_text’)
- classmethod load(path: str | pathlib.Path) simvx.core.scene_io.scene_file.SceneFile[source]¶
Read
pathand parse it.Any Python the interpreter runs opens here. Source it will not compile raises :class:
~simvx.core.scene_io.syntax.UnsupportedSceneSyntaxErrorcarrying the parser’s own message and position. Raises- Class:
FileNotFoundErrorwhen the file is absent.
- classmethod from_source(text: str, *, path: pathlib.Path | None = None) simvx.core.scene_io.scene_file.SceneFile[source]¶
Parse already-loaded source
text.pathis recorded for :meth:saveand error messages but is not read. Refuses unreadable source the way :meth:loaddoes.
- classmethod from_runtime(root: simvx.core.node.Node, *, class_name: str | None = None, report: list[str] | None = None) simvx.core.scene_io.scene_file.SceneFile[source]¶
Greenfield: emit source for a live :class:
Nodetree, then parse it.The returned :class:
SceneFilehas nopathuntil :meth:saveis called with one.A value the emitter cannot write raises
- Class:
~simvx.core.scene_io.UnemittableValueError, because a file saved without it would load back as a different scene. Passreportto take the other policy instead: the file is built without those values and one message per refusal is appended to the list, which is what an editor wants – it can show them and leave the document marked unsaved rather than lose the user’s work to an exception.
- property path: pathlib.Path | None[source]¶
Path the file was loaded from / will be saved to, or
None.
- property document: simvx.core.scene_io.source.SourceDocument[source]¶
The source document every edit goes through.
- property imports: simvx.core.scene_io.scene_file.ImportSet[source]¶
Editable view of the file’s top-level imports.
- scene_class() simvx.core.scene_io.scene_file.SceneClass[source]¶
The single primary Node subclass in the file.
Raises :class:
AmbiguousSceneErrorif the file contains multiple Node subclasses; raises :class:ValueErrorif it contains none.
- all_scene_classes() list[simvx.core.scene_io.scene_file.SceneClass][source]¶
Every Node subclass defined in the file, in source order.
Used for diagnostics and IDE features. The typical scene has one. Detection mirrors :func:
primary_node_class_from_source’s rule (Node base +__init__or class-bodyPropertydescriptors).
- insert_top_level_class(name: str, base: str, *, body: str = 'pass', before: str | None = None) simvx.core.scene_io.scene_file.SceneClass[source]¶
Insert
class <name>(<base>): <body>at module scope.Auto-imports
basevia :class:ImportSet(defaults tosimvx.core). Placement is just before the existing scene class (or beforebeforewhen given) so the new definition sits between imports and the scene that uses it. Returns the new- Class:
SceneClassview.
Raises :class:
ValueErrorif a top-level class with the same name already exists.
- is_dirty() bool[source]¶
True iff :meth:
dumpdiffers from the original input text.Used by :class:
SceneModuleto skip writes for files that were opened but never edited.
- class simvx.core.scene_io.scene_file.SceneClass(file: simvx.core.scene_io.scene_file.SceneFile, decl: simvx.core.scene_io.source.ClassDecl)[source]¶
An editable view of one Node-subclass class definition inside a scene.
Initialization
- __slots__¶
(‘_file’, ‘_name’)
- set_base(old: str, new: str) None[source]¶
Write
newwhere the class derives fromold.oldis the base as the header spells it andnewreplaces exactly that: the rest of the header, and the rest of the file, are left as the author wrote them. What a caller owes besides this is the importnewneeds and the removal of the oneoldno longer does (:class:ImportSet). Raises :class:ValueErrorwhen the class does not derive fromold.
- get_property_default(name: str) str | None[source]¶
Source text of the Property’s default expression, or
None.Inherited Properties are not visible: use the runtime tree to observe inherited values.
- add_property(name: str, default_expr: str) None[source]¶
Insert
name = Property(default_expr)into the class body.Inserted after the class’s existing Property declarations, or at the top of its body when it has none. Auto-imports
Propertyvia the file’s :class:ImportSet.The declaration is written at the indentation of whatever it is anchored on – the last Property, or the class’s first statement – so the first Property a class is given lands in the body rather than at column zero (:meth:
~simvx.core.scene_io.source.ClassDecl.insert).
- set_property_default(name: str, default_expr: str) None[source]¶
Write
default_exprwhere the Property’s default is.A
Property()declared without one gets the default written in front of whatever else it passes, since a positional argument cannot follow a keyword and that is where the default belongs anyway.
- set_root_kwarg(name: str, value_expr: str) None[source]¶
Update or insert a kwarg in the root
super().__init__(...)call.
- child_var_names(*, receiver: str | None = 'self') list[str][source]¶
Variable names of all children added via
<receiver>.add_child(<var>), in source order.receiverdefaults to"self", the root’s own children. PassNonefor the whole tree the file builds: a grandchild is added on the variable its parent is bound to (panel.add_child(label)), so a reader that asks only aboutselfreports a file with children as a file with none.
- add_child(var_name: str, type_name: str, *, before: str | None = None, after: str | None = None, from_module: str | None = 'simvx.core', receiver: str = 'self', **kwarg_exprs: str) None[source]¶
Insert a child construction +
<receiver>.add_childpair into__init__.Position: appended at the end of the existing child block by default;
before=orafter=(mutually exclusive) places relative to another child.receiveris the local theadd_childcall is written on, and"self"– the root’s own children – is the default. Anything else is a local__init__already binds to a child, and the pair is appended after the last child that local already has, or after the statement that parented the local itself when it has none yet. A local the file does not bind is refused: the file would name a variable that does not exist.Auto-imports
type_namefromfrom_module(defaults tosimvx.core) when the name is not already imported under any alias. Callers placing user classes should passfrom_module=type(node).__module__.from_module=Noneimports nothing, which is what a class this file defines itself is owed: an import of it either names a module nothing can resolve or runs this very file a second time, and the class the second run defines is not the one the scene was built from.Raises :class:
ValueErrorifvar_namealready exists in the__init__body or if bothbeforeandafterare passed.A construction too wide for the line limit is written across several lines, one keyword argument each, as a formatter would leave it: the column it is being inserted at is known here, and that is what the layout is measured against (:func:
~simvx.core.scene_io.layout.wrap_statement).Note: when the source is procedural (children built inside loops or conditionals: see :func:
has_procedural_construction), the inserted statements are appended at the top level of__init__and may execute in a surprising order relative to the procedural code.
- remove_child(var_name: str) simvx.core.scene_io.scene_file.Removed[source]¶
Remove the child’s own statements and everything left standing on them.
The assignment and the
self.add_childline are the two the emitter writes, but they are not the whole of what a child can own. A child of its own is written as a statement on its variable (child_0.add_child(label_0)), and so is anything else the author hung off it, so taking only the two would leave statements naming a variable that is gone and a file that raises on import. Every statement written on__init__’s own body goes if it names the variable, and so do the constructions those statements were the only use of, however far that propagates.A block is where this stops. A name used inside a
for, anif, awithor atryis not one statement to take out, and taking the block whole would delete work of the author’s that the child is only a part of, so the removal is refused: nothing is taken out, and- Class:
ValueErrornames the blocks that hold it back.- Meth:
_removal_blockersasks the same question without attempting the removal, for a caller that would rather announce it than raise.
Returns what went with it beyond the child’s own two lines (:class:
Removed): every other statement the sweep took, and the attribute bindings they carried off. Both are what a caller with somewhere to say so should say, since the author wrote those lines and a method readingself.<name>no longer has one.Does not auto-clean unused imports: that is the caller’s responsibility (use :meth:
ImportSet.remove). Raises- Class:
ValueErrorifvar_nameis absent.
- rename_child(old: str, new: str) None[source]¶
Rename the local variable a child is bound to, everywhere
__init__names it.Everywhere, not just on the two statements the emitter writes: an author may have hung a child of its own off the variable (
child_0.add_child(label_0)), read it further down, or used it inside aforor anif, and a rename that reached only the binding and theadd_childcall would leave those naming a variable the file no longer has. A local is one name for the whole function, so every place__init__writes it, at whatever depth, is the same variable and follows.
- reorder_children(order: list[str], *, receiver: str = 'self') None[source]¶
Reorder child assignment +
add_childline pairs to matchorder.Every existing child of
receiverthat the file binds a variable to must appear inorderexactly once. A child the file constructs inside its ownadd_childcall binds no variable and so cannot be named here at all: :meth:reorder_children_by_statementis the mover that can move one, and this is a thin way of asking for it by name.receiversays whose children are being reordered.
- reorder_children_by_statement(order: list[list[simvx.core.scene_io.source.Statement]], *, receiver: str = 'self') None[source]¶
Reorder
receiver’s children so theiradd_childstatements run inorder.orderholds one group per child, in the order the file should add them. A group is the statements that move together – the construction the file binds to a variable, where there is one, and theadd_childline itself – and between them the groups must hold everyadd_childstatement written onreceiverexactly once.A child the file constructs inside the call is movable too, which is why this exists beside :meth:
reorder_children.self.add_child( make_sprite("A"))binds no variable to name it by, but it is a whole line and moving it is exactly as safe as moving a bound child’s pair. What genuinely blocks a move is a statement sharing a line with another one, since the line cannot follow the child without taking that statement along, and that is refused here rather than written wrong.- Meth:
_children_share_linesasks the same question without attempting the move.
Every moved line carries the comments and blank lines written above it, except that whatever stood above the block stays at the top of it. A statement of the author’s written between two children still ends up after all of them: the children are re-inserted contiguously, which is the shape the emitter writes and the only one this can restore. That is a real cost and the round-trip design turns it into a refusal; until then it is what a reorder does.
- class simvx.core.scene_io.scene_file.ImportSet(document: simvx.core.scene_io.source.SourceDocument)[source]¶
Editable view of the file’s top-level imports.
The names a scene file imports, read and written as names rather than as text: what a line looks like once a name has been merged into it or taken out of it is the source layer’s business (:meth:
~simvx.core.scene_io.source.SourceDocument.ensure_import).Initialization
- __slots__¶
(‘_document’,)
- ensure(name: str, *, from_: str | None = None) None[source]¶
Add
import nameorfrom <from_> import nameif absent.When
from_matches an existingfrom <from_> import …line, the new name is merged into that line (sorted, deduplicated) instead of creating a separate import line, and that line is laid out to the width the rest of the file is written at.