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.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. 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)