simvx.core.scene_io.scene_file¶
High-level scene-shaped editing surface for parso-parsed sources.
This is Tier 3b of the scene I/O layer. It composes the lossless parse
(:mod:source_tree) and prefix-preserving primitives (:mod:edits) 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(source_tree: simvx.core.scene_io.source_tree.SourceTree, *, path: pathlib.Path | None)[source]¶
A parsed Python scene file with byte-perfect round-trip save.
Holds a parso tree plus the original on-disk text. All edits operate on the parso tree; :meth:
savewritestree.get_code(). Round-trip identity is guaranteed when no edits were made.Initialization
- __slots__¶
(‘_source_tree’, ‘_path’, ‘_imports’, ‘_original_text’)
- classmethod load(path: str | pathlib.Path) simvx.core.scene_io.scene_file.SceneFile[source]¶
Read
pathand parse it.A scene using one of the constructs the format documents as unreadable (:mod:
simvx.core.scene_io.syntax) opens here and refuses its edits; source broken any other way raises- Class:
~simvx.core.scene_io.syntax.UnsupportedSceneSyntaxError, naming the place rather than surfacing parso’s own token 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 source_tree: simvx.core.scene_io.source_tree.SourceTree[source]¶
Underlying lossless :class:
SourceTree.
- 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, class_node: parso.python.tree.Class)[source]¶
An editable view of one Node-subclass class definition inside a scene.
Initialization
- __slots__¶
(‘_file’, ‘_class’)
- unreadable_syntax() simvx.core.scene_io.syntax.SyntaxIssue | None[source]¶
The first construct in
__init__the parser could not read, if any.Nonefor the ordinary scene, which is every scene that stays inside the documented subset (:mod:simvx.core.scene_io.syntax). A caller about to rewrite__init__as a whole – the editor’s save – asks this first and leaves the file alone, because the individual edits it would make each look reasonable and the file they add up to does not run.
- 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 existing class-level Property declarations. Auto- imports
Propertyvia the file’s :class:ImportSet.
- 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[parso.tree.NodeOrLeaf], *, receiver: str = 'self') None[source]¶
Reorder
receiver’s children so theiradd_childstatements run inorder.orderholds everyadd_childstatement written onreceiver, exactly once each, in the order the file should call them. What moves with each is the child’s own lines and nothing else: the construction the file binds to a variable, where there is one, and theadd_childline itself.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.
A statement of the author’s written between two children 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(source_tree: simvx.core.scene_io.source_tree.SourceTree)[source]¶
Editable view of the file’s top-level imports.
Initialization
- __slots__¶
(‘_source_tree’,)
- 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.