meltygui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Dict, List, Tuple
|
|
7
|
+
|
|
8
|
+
from meltygui.utils.render_utils import print_stack_trace
|
|
9
|
+
|
|
10
|
+
def find_repo_root(start_path: Path | str = None) -> Path:
|
|
11
|
+
# Check if __file is defined
|
|
12
|
+
if '__file__' in globals():
|
|
13
|
+
if start_path is None:
|
|
14
|
+
start_path = Path(os.path.dirname(os.path.abspath(__file__)))
|
|
15
|
+
|
|
16
|
+
# If start_path is not provided, use the current working directory
|
|
17
|
+
if start_path is None:
|
|
18
|
+
start_path = os.getcwd()
|
|
19
|
+
|
|
20
|
+
current = Path(start_path).resolve()
|
|
21
|
+
|
|
22
|
+
while current != current.parent:
|
|
23
|
+
if (current / '.git').exists():
|
|
24
|
+
return current
|
|
25
|
+
current = current.parent
|
|
26
|
+
|
|
27
|
+
# If we reach here, we didn't find a .git directory, use module path
|
|
28
|
+
module_path = Path(__file__).resolve().parents[1]
|
|
29
|
+
print(f"Warning: No .git directory found. Using module path: {module_path}")
|
|
30
|
+
return module_path.parent if module_path.parent.exists() else module_path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_nested_classes(parent_class: type, parent_path: str) -> List[Tuple[str, type]]:
|
|
34
|
+
"""
|
|
35
|
+
Recursively find all nested classes within a class.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
parent_class: The parent class to search in
|
|
39
|
+
parent_path: The full path of the parent class
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A list of tuples (full_class_path, class_object) for nested classes
|
|
43
|
+
"""
|
|
44
|
+
nested_classes = []
|
|
45
|
+
|
|
46
|
+
# Check all attributes of the class
|
|
47
|
+
for name, obj in parent_class.__dict__.items():
|
|
48
|
+
# Skip special methods, private attributes, and non-classes
|
|
49
|
+
if name.startswith('__'):
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
if not isinstance(obj, type):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Build the path for this nested class
|
|
56
|
+
class_path = f"{parent_path}.{name}"
|
|
57
|
+
nested_classes.append((class_path, obj))
|
|
58
|
+
|
|
59
|
+
# Recursively find classes nested within this class
|
|
60
|
+
inner_classes = find_nested_classes(obj, class_path)
|
|
61
|
+
nested_classes.extend(inner_classes)
|
|
62
|
+
|
|
63
|
+
return nested_classes
|
|
64
|
+
|
|
65
|
+
def singleton(cls):
|
|
66
|
+
instances = {}
|
|
67
|
+
|
|
68
|
+
def get_instance(*args, **kwargs):
|
|
69
|
+
if cls not in instances:
|
|
70
|
+
instances[cls] = cls(*args, **kwargs)
|
|
71
|
+
return instances[cls]
|
|
72
|
+
|
|
73
|
+
return get_instance
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@singleton
|
|
77
|
+
class ClassUtility:
|
|
78
|
+
|
|
79
|
+
def __init__(self):
|
|
80
|
+
self.class_names: Optional[Dict[str, str]] = None
|
|
81
|
+
self.modules_imported = set()
|
|
82
|
+
self.root = None
|
|
83
|
+
self.initialized_modules = {}
|
|
84
|
+
self._module_count = -1
|
|
85
|
+
|
|
86
|
+
def initialize_class_names(self, root=None):
|
|
87
|
+
if self.class_names is not None and self._module_count == len(sys.modules):
|
|
88
|
+
return
|
|
89
|
+
self._module_count = len(sys.modules)
|
|
90
|
+
self.class_names = {}
|
|
91
|
+
for module_name, module in list(sys.modules.items()):
|
|
92
|
+
if module is None:
|
|
93
|
+
continue
|
|
94
|
+
for name, value in list(vars(module).items()):
|
|
95
|
+
if isinstance(value, type) and value.__module__ == module_name:
|
|
96
|
+
self.class_names.setdefault(name, f"{module_name}.{name}")
|
|
97
|
+
for nested_path, nested in find_nested_classes(value, f"{module_name}.{name}"):
|
|
98
|
+
self.class_names.setdefault('.'.join(nested_path.split('.')[-2:]), nested_path)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def find_all_classes(root_dir: str, package_name: str = None) -> List[Tuple[str, str]]:
|
|
103
|
+
"""
|
|
104
|
+
Find all classes in a package directory structure, including nested directories and nested classes.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
root_dir: The root directory to search in
|
|
108
|
+
package_name: Optional base package name prefix
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A list of tuples (full_class_path, class_object)
|
|
112
|
+
"""
|
|
113
|
+
classes = []
|
|
114
|
+
visited_modules = set()
|
|
115
|
+
|
|
116
|
+
# Check if path exists
|
|
117
|
+
if not os.path.exists(root_dir):
|
|
118
|
+
root_dir = os.path.abspath(f"./{root_dir}")
|
|
119
|
+
|
|
120
|
+
if not os.path.isdir(root_dir):
|
|
121
|
+
print(f"Error: {root_dir} is not a directory")
|
|
122
|
+
# Print stack
|
|
123
|
+
print_stack_trace()
|
|
124
|
+
return classes
|
|
125
|
+
|
|
126
|
+
# Walk through all Python files in the directory structure
|
|
127
|
+
for dirpath, dirnames, filenames in os.walk(root_dir):
|
|
128
|
+
# Skip __pycache__ and other hidden directories
|
|
129
|
+
dirnames[:] = [d for d in dirnames if not d.startswith('__') and not d.startswith('.')]
|
|
130
|
+
|
|
131
|
+
# Get the relative path from the root directory
|
|
132
|
+
rel_path = os.path.relpath(dirpath, root_dir)
|
|
133
|
+
|
|
134
|
+
# Convert directory path to module path
|
|
135
|
+
if rel_path == '.':
|
|
136
|
+
module_prefix = package_name or os.path.basename(root_dir)
|
|
137
|
+
else:
|
|
138
|
+
module_prefix = f"{package_name or os.path.basename(root_dir)}.{rel_path.replace(os.sep, '.')}"
|
|
139
|
+
|
|
140
|
+
# Process Python files in this directory
|
|
141
|
+
for filename in filenames:
|
|
142
|
+
if filename.endswith('.py') and not filename.startswith('__'):
|
|
143
|
+
module_name = f"{module_prefix}.{filename[:-3]}"
|
|
144
|
+
|
|
145
|
+
# Skip already visited modules
|
|
146
|
+
if module_name in visited_modules:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
visited_modules.add(module_name)
|
|
150
|
+
if "app_model" in module_name:
|
|
151
|
+
pass
|
|
152
|
+
try:
|
|
153
|
+
# Import the module
|
|
154
|
+
module = sys.modules.get(module_name)
|
|
155
|
+
# importlib.reload(module)
|
|
156
|
+
|
|
157
|
+
# Find top-level classes in this module and add them
|
|
158
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
159
|
+
# Only include classes defined in this module (not imported)
|
|
160
|
+
# Add class
|
|
161
|
+
if obj.__module__ == module_name:
|
|
162
|
+
class_path = f"{module_name}.{name}"
|
|
163
|
+
parts = class_path.split('.')
|
|
164
|
+
classes.append((parts[-1], class_path))
|
|
165
|
+
|
|
166
|
+
# Now find nested classes
|
|
167
|
+
nested_classes = find_nested_classes(obj, class_path)
|
|
168
|
+
if len(nested_classes) > 0:
|
|
169
|
+
for nested_class_path, nested_class in nested_classes:
|
|
170
|
+
nested_parts = nested_class_path.split('.')
|
|
171
|
+
nested_key = f"{nested_parts[-2]}.{nested_parts[-1]}"
|
|
172
|
+
classes.append((nested_key, nested_class_path))
|
|
173
|
+
|
|
174
|
+
except (ImportError, ModuleNotFoundError) as e:
|
|
175
|
+
print(f"Error importing {module_name}: {e}")
|
|
176
|
+
|
|
177
|
+
return classes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from meltygui.core.conversion.dict_conversion import DictConversion
|
|
2
|
+
from meltygui.utils.render_utils import LSDView
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DynamicObj(DictConversion):
|
|
6
|
+
"""Simplified class that mirrors driver dict keys as attributes in real-time."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, driver_dict=None):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.driver = None
|
|
11
|
+
self._root = None
|
|
12
|
+
self.not_real = set()
|
|
13
|
+
|
|
14
|
+
# Store driver using super().__setattr__ to avoid recursion
|
|
15
|
+
super().__setattr__('driver', driver_dict if driver_dict is not None else {})
|
|
16
|
+
|
|
17
|
+
def __getattr__(self, name):
|
|
18
|
+
if name == 'driver' or name == '_root':
|
|
19
|
+
# Return driver or root directly
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
# Only called when attribute doesn't exist
|
|
23
|
+
# Check if it's a driver key
|
|
24
|
+
if self._root is not None:
|
|
25
|
+
driver = self._root.get(self.driver)
|
|
26
|
+
else:
|
|
27
|
+
real_dict = object.__getattribute__(self, '__dict__')
|
|
28
|
+
if name not in real_dict:
|
|
29
|
+
# Create the attribute with None value
|
|
30
|
+
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
31
|
+
else:
|
|
32
|
+
return real_dict[name]
|
|
33
|
+
# Todo: dynamic add
|
|
34
|
+
if hasattr(self, 'driver') and name in driver:
|
|
35
|
+
real_dict = object.__getattribute__(self, '__dict__')
|
|
36
|
+
if name not in real_dict:
|
|
37
|
+
# Create the attribute with None value
|
|
38
|
+
return None
|
|
39
|
+
else:
|
|
40
|
+
return real_dict[name]
|
|
41
|
+
|
|
42
|
+
# If not in driver, raise AttributeError
|
|
43
|
+
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
44
|
+
|
|
45
|
+
# has attrib check
|
|
46
|
+
def __hasattr__(self, name):
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def __getattribute__(self, name):
|
|
50
|
+
if name == 'driver' or name == '_root':
|
|
51
|
+
# Return driver or root directly
|
|
52
|
+
return super().__getattribute__(name)
|
|
53
|
+
|
|
54
|
+
# Intercept __dict__ access
|
|
55
|
+
if self.driver is not None and self._root is not None:
|
|
56
|
+
driver = self._root.get(self.driver)
|
|
57
|
+
else:
|
|
58
|
+
driver = {}
|
|
59
|
+
|
|
60
|
+
if name == '__dict__':
|
|
61
|
+
# Get real dict without recursion
|
|
62
|
+
real_dict = object.__getattribute__(self, '__dict__')
|
|
63
|
+
|
|
64
|
+
for key in driver:
|
|
65
|
+
if key not in real_dict:
|
|
66
|
+
setattr(self, key, None)
|
|
67
|
+
self.not_real.add(key)
|
|
68
|
+
|
|
69
|
+
to_remove = set()
|
|
70
|
+
for key in self.not_real:
|
|
71
|
+
# Indicates that the attribute was removed
|
|
72
|
+
if key not in driver:
|
|
73
|
+
to_remove.add(key)
|
|
74
|
+
|
|
75
|
+
for key in to_remove:
|
|
76
|
+
self.not_real.remove(key)
|
|
77
|
+
if key in real_dict:
|
|
78
|
+
real_dict.pop(key)
|
|
79
|
+
|
|
80
|
+
# Only called when attribute doesn't exist
|
|
81
|
+
# Check if it's a driver key
|
|
82
|
+
|
|
83
|
+
# if name != 'driver' and name != '_root' and name in driver:
|
|
84
|
+
# # Create the attribute with None value
|
|
85
|
+
# setattr(self, name, None)
|
|
86
|
+
# return None
|
|
87
|
+
|
|
88
|
+
# For everything else, use default behavior
|
|
89
|
+
return super().__getattribute__(name)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
graph_compare — structural + sharing-topology equality for object graphs.
|
|
3
|
+
|
|
4
|
+
This is the acceptance oracle for the load/save work: it proves that a
|
|
5
|
+
round-tripped graph reproduces the original. "Reproduces" means three things,
|
|
6
|
+
in increasing strength:
|
|
7
|
+
|
|
8
|
+
1. same class at every node,
|
|
9
|
+
2. same *serialized* public field values (the fields load/save round-trips),
|
|
10
|
+
3. same SHARING TOPOLOGY — if two paths in graph A reach one shared object,
|
|
11
|
+
the same two paths in graph B must reach one shared object too (and cycles
|
|
12
|
+
close at the same place). This is the "object reference tree" property the
|
|
13
|
+
whole exercise is about; plain field-equality misses it.
|
|
14
|
+
|
|
15
|
+
It deliberately compares only the fields that load/save is responsible for
|
|
16
|
+
(public, non-excluded, non-underscore) so that dropped transient state
|
|
17
|
+
(_parent weakrefs, GL handles, lambdas) doesn't register as a difference.
|
|
18
|
+
"""
|
|
19
|
+
from enum import Enum
|
|
20
|
+
|
|
21
|
+
_PRIMITIVE = (int, float, bool, str, bytes, type(None))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Single source of truth: reuse v2's exact suppression set so the oracle can't
|
|
25
|
+
# drift from what's actually serialized (v1). @exclude/__excluded_attrs__ is NOT
|
|
26
|
+
# here (it only gates @live invalidation), so type/name/id is compared.
|
|
27
|
+
from meltygui.core.conversion.load_save_v2 import _suppressed as _v2_suppressed
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _public_keys(obj, excluded=()):
|
|
31
|
+
"""The attribute names load/save is responsible for: public, not suppressed.
|
|
32
|
+
`excluded` mirrors the caller-level drop list passed to v2.dumps, so fields the
|
|
33
|
+
save deliberately omits aren't flagged as round-trip diffs.
|
|
34
|
+
|
|
35
|
+
Iterates the DEFAULT instance's keys, matching v2._save_state's delta encoding
|
|
36
|
+
(which serializes only fields present on a fresh default). Fields that exist on
|
|
37
|
+
`obj` but not on the default are runtime-injected and intentionally NOT
|
|
38
|
+
round-tripped (to_dict drops them too), so they must not register as diffs."""
|
|
39
|
+
excl = _v2_suppressed(obj, excluded)
|
|
40
|
+
cls = type(obj)
|
|
41
|
+
default = getattr(cls, "default_instance", None)
|
|
42
|
+
src = default if default is not None else obj
|
|
43
|
+
return sorted(k for k in vars(src) if not k.startswith("_") and k not in excl)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Diff(list):
|
|
47
|
+
"""A list of human-readable difference strings; truthy iff graphs differ."""
|
|
48
|
+
def report(self, limit=40):
|
|
49
|
+
if not self:
|
|
50
|
+
return "graphs are structurally equivalent (incl. sharing topology)"
|
|
51
|
+
head = self[:limit]
|
|
52
|
+
extra = f"\n ... and {len(self) - limit} more" if len(self) > limit else ""
|
|
53
|
+
return f"{len(self)} difference(s):\n " + "\n ".join(head) + extra
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def compare(a, b, *, ignore=(), excluded=()):
|
|
57
|
+
"""Return a Diff describing how graph ``b`` deviates from graph ``a``.
|
|
58
|
+
|
|
59
|
+
``ignore`` is a set of attribute names skipped everywhere (e.g. {'id'} when
|
|
60
|
+
ids are intentionally regenerated on load).
|
|
61
|
+
``excluded`` is the caller-level field-drop list passed to v2.dumps — those
|
|
62
|
+
fields aren't serialized, so they're not compared.
|
|
63
|
+
"""
|
|
64
|
+
diffs = Diff()
|
|
65
|
+
ignore = set(ignore)
|
|
66
|
+
excluded = frozenset(excluded)
|
|
67
|
+
|
|
68
|
+
# identity maps: object in A -> object in B that it first paired with.
|
|
69
|
+
# En catch sharing-topology divergence: if A-node X pairs with B-node Y once,
|
|
70
|
+
# any later encounter of X must again pair with Y (and vice-versa), else the
|
|
71
|
+
# aliasing/cycle structure differs between the two graphs.
|
|
72
|
+
a_to_b = {}
|
|
73
|
+
b_to_a = {}
|
|
74
|
+
|
|
75
|
+
def walk(x, y, path):
|
|
76
|
+
# --- primitives & enums: value equality ---
|
|
77
|
+
# Compare enums by class QUALNAME + member name (not class identity): a
|
|
78
|
+
# load/save round-trip can legitimately land an equivalent class object
|
|
79
|
+
# under a different module identity; what matters is same enum, same member.
|
|
80
|
+
if isinstance(x, Enum) or isinstance(y, Enum):
|
|
81
|
+
xt = type(x).__qualname__ if isinstance(x, Enum) else f"<non-enum {type(x).__name__}>"
|
|
82
|
+
yt = type(y).__qualname__ if isinstance(y, Enum) else f"<non-enum {type(y).__name__}>"
|
|
83
|
+
if xt != yt or getattr(x, "name", None) != getattr(y, "name", None):
|
|
84
|
+
diffs.append(f"{path}: enum {xt}.{getattr(x,'name','?')} != {yt}.{getattr(y,'name','?')}")
|
|
85
|
+
return
|
|
86
|
+
if isinstance(x, _PRIMITIVE) or isinstance(y, _PRIMITIVE):
|
|
87
|
+
if type(x) is not type(y) or x != y:
|
|
88
|
+
diffs.append(f"{path}: {x!r} ({type(x).__name__}) != {y!r} ({type(y).__name__})")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# --- sharing topology: enforce for consistent bijection on non-primitives ---
|
|
92
|
+
xid, yid = id(x), id(y)
|
|
93
|
+
if xid in a_to_b:
|
|
94
|
+
if a_to_b[xid] is not y:
|
|
95
|
+
diffs.append(f"{path}: sharing diverged — A node reused but B node differs "
|
|
96
|
+
f"(A first paired at a different B object)")
|
|
97
|
+
return # already fully compared on first encounter
|
|
98
|
+
if yid in b_to_a and b_to_a[yid] is not x:
|
|
99
|
+
diffs.append(f"{path}: sharing diverged — B node is shared but A node is not")
|
|
100
|
+
return
|
|
101
|
+
a_to_b[xid] = y
|
|
102
|
+
b_to_a[yid] = x
|
|
103
|
+
|
|
104
|
+
# --- callables / types: compare by reference identity of name ---
|
|
105
|
+
if callable(x) or isinstance(x, type):
|
|
106
|
+
xn = getattr(x, "__qualname__", repr(x))
|
|
107
|
+
yn = getattr(y, "__qualname__", repr(y))
|
|
108
|
+
if xn != yn:
|
|
109
|
+
diffs.append(f"{path}: callable/type {xn} != {yn}")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# --- containers ---
|
|
113
|
+
if isinstance(x, dict) or isinstance(y, dict):
|
|
114
|
+
if not (isinstance(x, dict) and isinstance(y, dict)):
|
|
115
|
+
diffs.append(f"{path}: dict vs non-dict ({type(x).__name__}/{type(y).__name__})")
|
|
116
|
+
return
|
|
117
|
+
xk, yk = set(x.keys()), set(y.keys())
|
|
118
|
+
if xk != yk:
|
|
119
|
+
only_a = sorted(map(repr, xk - yk))[:6]
|
|
120
|
+
only_b = sorted(map(repr, yk - xk))[:6]
|
|
121
|
+
diffs.append(f"{path}: dict keys differ only-A={only_a} only-B={only_b}")
|
|
122
|
+
for k in xk & yk:
|
|
123
|
+
walk(x[k], y[k], f"{path}[{k!r}]")
|
|
124
|
+
return
|
|
125
|
+
if isinstance(x, (list, tuple)) or isinstance(y, (list, tuple)):
|
|
126
|
+
if type(x) is not type(y):
|
|
127
|
+
diffs.append(f"{path}: seq type {type(x).__name__} != {type(y).__name__}")
|
|
128
|
+
return
|
|
129
|
+
if len(x) != len(y):
|
|
130
|
+
diffs.append(f"{path}: length {len(x)} != {len(y)}")
|
|
131
|
+
for i in range(min(len(x), len(y))):
|
|
132
|
+
walk(x[i], y[i], f"{path}[{i}]")
|
|
133
|
+
return
|
|
134
|
+
if isinstance(x, (set, frozenset)) or isinstance(y, (set, frozenset)):
|
|
135
|
+
if x != y:
|
|
136
|
+
diffs.append(f"{path}: set contents differ")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# --- objects by attributes ---
|
|
140
|
+
if type(x) is not type(y):
|
|
141
|
+
diffs.append(f"{path}: class {type(x).__name__} != {type(y).__name__}")
|
|
142
|
+
return
|
|
143
|
+
if not hasattr(x, "__dict__"):
|
|
144
|
+
if x != y:
|
|
145
|
+
diffs.append(f"{path}: {type(x).__name__} {x!r} != {y!r}")
|
|
146
|
+
return
|
|
147
|
+
ka = [k for k in _public_keys(x, excluded) if k not in ignore]
|
|
148
|
+
kb = [k for k in _public_keys(y, excluded) if k not in ignore]
|
|
149
|
+
if ka != kb:
|
|
150
|
+
sa, sb = set(ka), set(kb)
|
|
151
|
+
diffs.append(f"{path} <{type(x).__name__}>: fields differ "
|
|
152
|
+
f"only-A={sorted(sa - sb)} only-B={sorted(sb - sa)}")
|
|
153
|
+
for k in ka:
|
|
154
|
+
if k in ignore or k not in kb:
|
|
155
|
+
continue
|
|
156
|
+
walk(getattr(x, k), getattr(y, k), f"{path}.{k}")
|
|
157
|
+
|
|
158
|
+
walk(a, b, "root")
|
|
159
|
+
return diffs
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def count_nodes(obj):
|
|
163
|
+
"""How many distinct non-primitive objects the graph reaches (sanity sizing)."""
|
|
164
|
+
seen = set()
|
|
165
|
+
|
|
166
|
+
def walk(o):
|
|
167
|
+
if isinstance(o, _PRIMITIVE) or isinstance(o, Enum):
|
|
168
|
+
return
|
|
169
|
+
if id(o) in seen:
|
|
170
|
+
return
|
|
171
|
+
seen.add(id(o))
|
|
172
|
+
if isinstance(o, dict):
|
|
173
|
+
for v in o.values():
|
|
174
|
+
walk(v)
|
|
175
|
+
elif isinstance(o, (list, tuple, set, frozenset)):
|
|
176
|
+
for v in o:
|
|
177
|
+
walk(v)
|
|
178
|
+
elif hasattr(o, "__dict__"):
|
|
179
|
+
for k in _public_keys(o):
|
|
180
|
+
walk(getattr(o, k))
|
|
181
|
+
|
|
182
|
+
walk(obj)
|
|
183
|
+
return len(seen)
|