pywry 2.0.2__tar.gz → 2.0.4__tar.gz

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 (279) hide show
  1. {pywry-2.0.2 → pywry-2.0.4}/PKG-INFO +1 -1
  2. {pywry-2.0.2 → pywry-2.0.4}/pyproject.toml +1 -1
  3. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-grid-35.0.0.css.gz +0 -0
  4. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-grid-community-35.0.0.min.js.gz +0 -0
  5. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-alpine-dark-35.0.0.css.gz +0 -0
  6. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-alpine-light-35.0.0.css.gz +0 -0
  7. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-balham-dark-35.0.0.css.gz +0 -0
  8. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-balham-light-35.0.0.css.gz +0 -0
  9. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-material-dark-35.0.0.css.gz +0 -0
  10. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-material-light-35.0.0.css.gz +0 -0
  11. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-quartz-dark-35.0.0.css.gz +0 -0
  12. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-quartz-light-35.0.0.css.gz +0 -0
  13. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/lightweight-charts-5.1.0.standalone.production.js.gz +0 -0
  14. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/plotly-3.3.1.js.gz +0 -0
  15. {pywry-2.0.2 → pywry-2.0.4}/pywry/inline.py +214 -30
  16. {pywry-2.0.2 → pywry-2.0.4}/pywry/notebook.py +108 -0
  17. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/redis.py +31 -6
  18. {pywry-2.0.2 → pywry-2.0.4}/.gitignore +0 -0
  19. {pywry-2.0.2 → pywry-2.0.4}/AGENTS.md +0 -0
  20. {pywry-2.0.2 → pywry-2.0.4}/README.md +0 -0
  21. {pywry-2.0.2 → pywry-2.0.4}/hatch_build.py +0 -0
  22. {pywry-2.0.2 → pywry-2.0.4}/pywry/Tauri.toml +0 -0
  23. {pywry-2.0.2 → pywry-2.0.4}/pywry/__init__.py +0 -0
  24. {pywry-2.0.2 → pywry-2.0.4}/pywry/__main__.py +0 -0
  25. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/.claude-plugin/marketplace.json +0 -0
  26. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/.claude-plugin/plugin.json +0 -0
  27. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/.mcp.json +0 -0
  28. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/CHANGELOG.md +0 -0
  29. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/PRIVACY.md +0 -0
  30. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/README.md +0 -0
  31. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/RELEASING.md +0 -0
  32. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/agents/pywry-builder.md +0 -0
  33. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/commands/doctor.md +0 -0
  34. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/commands/examples.md +0 -0
  35. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/commands/scaffold.md +0 -0
  36. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/hooks/hooks.json +0 -0
  37. {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/skills/pywry-orientation/SKILL.md +0 -0
  38. {pywry-2.0.2 → pywry-2.0.4}/pywry/_freeze.py +0 -0
  39. {pywry-2.0.2 → pywry-2.0.4}/pywry/_pyinstaller_hook/__init__.py +0 -0
  40. {pywry-2.0.2 → pywry-2.0.4}/pywry/_pyinstaller_hook/hook-pywry.py +0 -0
  41. {pywry-2.0.2 → pywry-2.0.4}/pywry/_vendor/__init__.py +0 -0
  42. {pywry-2.0.2 → pywry-2.0.4}/pywry/app.py +0 -0
  43. {pywry-2.0.2 → pywry-2.0.4}/pywry/asset_loader.py +0 -0
  44. {pywry-2.0.2 → pywry-2.0.4}/pywry/assets.py +0 -0
  45. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/__init__.py +0 -0
  46. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/callback_server.py +0 -0
  47. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/deploy_routes.py +0 -0
  48. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/flow.py +0 -0
  49. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/login_page.py +0 -0
  50. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/pkce.py +0 -0
  51. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/providers.py +0 -0
  52. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/session.py +0 -0
  53. {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/token_store.py +0 -0
  54. {pywry-2.0.2 → pywry-2.0.4}/pywry/callbacks.py +0 -0
  55. {pywry-2.0.2 → pywry-2.0.4}/pywry/capabilities/default.toml +0 -0
  56. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/__init__.py +0 -0
  57. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/artifacts.py +0 -0
  58. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/html.py +0 -0
  59. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/manager.py +0 -0
  60. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/models.py +0 -0
  61. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/permissions.py +0 -0
  62. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/__init__.py +0 -0
  63. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/anthropic.py +0 -0
  64. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/callback.py +0 -0
  65. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/deepagent.py +0 -0
  66. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/magentic.py +0 -0
  67. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/openai.py +0 -0
  68. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/stdio.py +0 -0
  69. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/session.py +0 -0
  70. {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/updates.py +0 -0
  71. {pywry-2.0.2 → pywry-2.0.4}/pywry/cli.py +0 -0
  72. {pywry-2.0.2 → pywry-2.0.4}/pywry/commands/__init__.py +0 -0
  73. {pywry-2.0.2 → pywry-2.0.4}/pywry/commands/window_commands.py +0 -0
  74. {pywry-2.0.2 → pywry-2.0.4}/pywry/config.py +0 -0
  75. {pywry-2.0.2 → pywry-2.0.4}/pywry/exceptions.py +0 -0
  76. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry-dark.svg +0 -0
  77. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry-icon.svg +0 -0
  78. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry-light.svg +0 -0
  79. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry.png +0 -0
  80. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/icon.icns +0 -0
  81. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/icon.ico +0 -0
  82. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/icon.png +0 -0
  83. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/128x128.png +0 -0
  84. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/128x128@2x.png +0 -0
  85. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/32x32.png +0 -0
  86. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/64x64.png +0 -0
  87. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square107x107Logo.png +0 -0
  88. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square142x142Logo.png +0 -0
  89. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square150x150Logo.png +0 -0
  90. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square284x284Logo.png +0 -0
  91. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square30x30Logo.png +0 -0
  92. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square310x310Logo.png +0 -0
  93. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square44x44Logo.png +0 -0
  94. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square71x71Logo.png +0 -0
  95. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square89x89Logo.png +0 -0
  96. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/StoreLogo.png +0 -0
  97. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  98. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-hdpi/ic_launcher.png +0 -0
  99. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  100. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-hdpi/ic_launcher_round.png +0 -0
  101. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-mdpi/ic_launcher.png +0 -0
  102. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  103. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-mdpi/ic_launcher_round.png +0 -0
  104. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xhdpi/ic_launcher.png +0 -0
  105. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  106. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xhdpi/ic_launcher_round.png +0 -0
  107. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxhdpi/ic_launcher.png +0 -0
  108. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  109. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  110. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxxhdpi/ic_launcher.png +0 -0
  111. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  112. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  113. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/values/ic_launcher_background.xml +0 -0
  114. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/icon.icns +0 -0
  115. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/icon.ico +0 -0
  116. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/icon.png +0 -0
  117. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@1x.png +0 -0
  118. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@2x-1.png +0 -0
  119. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@2x.png +0 -0
  120. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@3x.png +0 -0
  121. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@1x.png +0 -0
  122. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@2x-1.png +0 -0
  123. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@2x.png +0 -0
  124. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@3x.png +0 -0
  125. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@1x.png +0 -0
  126. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@2x-1.png +0 -0
  127. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@2x.png +0 -0
  128. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@3x.png +0 -0
  129. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-512@2x.png +0 -0
  130. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-60x60@2x.png +0 -0
  131. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-60x60@3x.png +0 -0
  132. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-76x76@1x.png +0 -0
  133. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-76x76@2x.png +0 -0
  134. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-83.5x83.5@2x.png +0 -0
  135. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/index.html +0 -0
  136. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/aggrid-defaults.js +0 -0
  137. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/auth-helpers.js +0 -0
  138. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/bridge.js +0 -0
  139. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/chat-handlers.js +0 -0
  140. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/cleanup.js +0 -0
  141. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/event-bridge.js +0 -0
  142. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/hot-reload.js +0 -0
  143. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/main.js +0 -0
  144. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/modal-handlers.js +0 -0
  145. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/plotly-defaults.js +0 -0
  146. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/plotly-templates.js +0 -0
  147. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/plotly-widget.js +0 -0
  148. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/scrollbar.js +0 -0
  149. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/system-events.js +0 -0
  150. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/theme-manager.js +0 -0
  151. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/toast-notifications.js +0 -0
  152. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/toolbar-bridge.js +0 -0
  153. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/toolbar-handlers.js +0 -0
  154. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tooltip-manager.js +0 -0
  155. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/01-globals.js +0 -0
  156. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/02-datafeed.js +0 -0
  157. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/03-theme.js +0 -0
  158. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/04-series.js +0 -0
  159. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/05-lifecycle.js +0 -0
  160. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/06-storage.js +0 -0
  161. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/00-state-tools.js +0 -0
  162. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/01-color-picker.js +0 -0
  163. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/02-helpers.js +0 -0
  164. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/03-utils.js +0 -0
  165. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/04-settings-apply.js +0 -0
  166. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/05-hit-test.js +0 -0
  167. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/06-render.js +0 -0
  168. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/07-toolbar-menu.js +0 -0
  169. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/08-mouse-tools.js +0 -0
  170. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/00-state-helpers.js +0 -0
  171. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/01-symbol-search.js +0 -0
  172. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/02-series-settings.js +0 -0
  173. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/03-volume-settings.js +0 -0
  174. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/04-chart-settings.js +0 -0
  175. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/05-compare.js +0 -0
  176. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/06-indicator-symbol-picker.js +0 -0
  177. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/07-drawing-settings.js +0 -0
  178. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/00-helpers-catalog.js +0 -0
  179. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/01-compute-basic.js +0 -0
  180. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/02-bb-primitive.js +0 -0
  181. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/03-ichimoku-primitive.js +0 -0
  182. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/04-volume-profile.js +0 -0
  183. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/05-compute-extra.js +0 -0
  184. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/06-indicator-helpers.js +0 -0
  185. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/07-remove-legend.js +0 -0
  186. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/08-pane-management.js +0 -0
  187. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/09-legend-rebuild.js +0 -0
  188. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/10-settings-dialog.js +0 -0
  189. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/11-apply-settings.js +0 -0
  190. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/12-panel.js +0 -0
  191. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/10-events.js +0 -0
  192. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/11-legend.js +0 -0
  193. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/12-session-tz.js +0 -0
  194. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart-widget.js +0 -0
  195. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/ws-bridge.js +0 -0
  196. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/chat.css +0 -0
  197. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/pywry.css +0 -0
  198. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/toast.css +0 -0
  199. {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/tvchart.css +0 -0
  200. {pywry-2.0.2 → pywry-2.0.4}/pywry/grid.py +0 -0
  201. {pywry-2.0.2 → pywry-2.0.4}/pywry/hot_reload.py +0 -0
  202. {pywry-2.0.2 → pywry-2.0.4}/pywry/log.py +0 -0
  203. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/__init__.py +0 -0
  204. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/__main__.py +0 -0
  205. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/agentic.py +0 -0
  206. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/app_artifact.py +0 -0
  207. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/builders.py +0 -0
  208. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/docs.py +0 -0
  209. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/handlers.py +0 -0
  210. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/install.py +0 -0
  211. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/prompts.py +0 -0
  212. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/resources.py +0 -0
  213. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/server.py +0 -0
  214. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/__init__.py +0 -0
  215. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/authentication/SKILL.md +0 -0
  216. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/autonomous_building/SKILL.md +0 -0
  217. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/chat/SKILL.md +0 -0
  218. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/chat_agent/SKILL.md +0 -0
  219. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/component_reference/SKILL.md +0 -0
  220. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/css_selectors/SKILL.md +0 -0
  221. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/data_visualization/SKILL.md +0 -0
  222. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/deploy/SKILL.md +0 -0
  223. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/events/SKILL.md +0 -0
  224. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/forms_and_inputs/SKILL.md +0 -0
  225. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/iframe/SKILL.md +0 -0
  226. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/interactive_buttons/SKILL.md +0 -0
  227. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/jupyter/SKILL.md +0 -0
  228. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/modals/SKILL.md +0 -0
  229. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/native/SKILL.md +0 -0
  230. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/styling/SKILL.md +0 -0
  231. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/tvchart/SKILL.md +0 -0
  232. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/state.py +0 -0
  233. {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/tools.py +0 -0
  234. {pywry-2.0.2 → pywry-2.0.4}/pywry/menu_proxy.py +0 -0
  235. {pywry-2.0.2 → pywry-2.0.4}/pywry/modal.py +0 -0
  236. {pywry-2.0.2 → pywry-2.0.4}/pywry/models.py +0 -0
  237. {pywry-2.0.2 → pywry-2.0.4}/pywry/plotly_config.py +0 -0
  238. {pywry-2.0.2 → pywry-2.0.4}/pywry/runtime.py +0 -0
  239. {pywry-2.0.2 → pywry-2.0.4}/pywry/scripts.py +0 -0
  240. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/__init__.py +0 -0
  241. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/_factory.py +0 -0
  242. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/auth.py +0 -0
  243. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/base.py +0 -0
  244. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/callbacks.py +0 -0
  245. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/file.py +0 -0
  246. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/memory.py +0 -0
  247. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/server.py +0 -0
  248. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/sqlite.py +0 -0
  249. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/sync_helpers.py +0 -0
  250. {pywry-2.0.2 → pywry-2.0.4}/pywry/state/types.py +0 -0
  251. {pywry-2.0.2 → pywry-2.0.4}/pywry/state_mixins.py +0 -0
  252. {pywry-2.0.2 → pywry-2.0.4}/pywry/templates.py +0 -0
  253. {pywry-2.0.2 → pywry-2.0.4}/pywry/toolbar.py +0 -0
  254. {pywry-2.0.2 → pywry-2.0.4}/pywry/tray_proxy.py +0 -0
  255. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/__init__.py +0 -0
  256. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/config.py +0 -0
  257. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/datafeed.py +0 -0
  258. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/mixin.py +0 -0
  259. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/models.py +0 -0
  260. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/normalize.py +0 -0
  261. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/toolbars.py +0 -0
  262. {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/udf.py +0 -0
  263. {pywry-2.0.2 → pywry-2.0.4}/pywry/types.py +0 -0
  264. {pywry-2.0.2 → pywry-2.0.4}/pywry/utils/__init__.py +0 -0
  265. {pywry-2.0.2 → pywry-2.0.4}/pywry/utils/async_helpers.py +0 -0
  266. {pywry-2.0.2 → pywry-2.0.4}/pywry/watcher.py +0 -0
  267. {pywry-2.0.2 → pywry-2.0.4}/pywry/widget.py +0 -0
  268. {pywry-2.0.2 → pywry-2.0.4}/pywry/widget_protocol.py +0 -0
  269. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_dispatch.py +0 -0
  270. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/__init__.py +0 -0
  271. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/controller.py +0 -0
  272. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/lifecycle.py +0 -0
  273. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/__init__.py +0 -0
  274. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/base.py +0 -0
  275. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/browser.py +0 -0
  276. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/multi_window.py +0 -0
  277. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/new_window.py +0 -0
  278. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/single_window.py +0 -0
  279. {pywry-2.0.2 → pywry-2.0.4}/pywry/window_proxy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywry
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: A lightweight and blazingly fast, cross-platform, WebView rendering engine and desktop UI toolkit for Python. Batteries included.
5
5
  Project-URL: Homepage, https://github.com/deeleeramone/PyWry
6
6
  Project-URL: Repository, https://github.com/deeleeramone/PyWry
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pywry"
3
- version = "2.0.2"
3
+ version = "2.0.4"
4
4
  description = "A lightweight and blazingly fast, cross-platform, WebView rendering engine and desktop UI toolkit for Python. Batteries included."
5
5
  authors = [{ name = "PyWry", email = "pywry2@gmail.com" }]
6
6
  license = { text = "Apache 2.0" }
@@ -41,6 +41,7 @@ from .state_mixins import (
41
41
  _UNSET,
42
42
  _Unset,
43
43
  )
44
+ from .tvchart.mixin import TVChartStateMixin
44
45
  from .toolbar import Toolbar, get_toolbar_script, wrap_content_with_toolbars
45
46
  from .widget_protocol import BaseWidget # noqa: TC001
46
47
 
@@ -1667,7 +1668,7 @@ async def get_widget_html_async(widget_id: str) -> str | None:
1667
1668
  return await _state.get_widget_html_async(widget_id)
1668
1669
 
1669
1670
 
1670
- class InlineWidget(GridStateMixin, PlotlyStateMixin, ToolbarStateMixin):
1671
+ class InlineWidget(GridStateMixin, PlotlyStateMixin, TVChartStateMixin, ToolbarStateMixin):
1671
1672
  """Base inline widget that renders via FastAPI server and IFrame.
1672
1673
 
1673
1674
  Implements BaseWidget protocol for unified API across rendering backends.
@@ -1822,7 +1823,10 @@ class InlineWidget(GridStateMixin, PlotlyStateMixin, ToolbarStateMixin):
1822
1823
  webbrowser.open(self.url)
1823
1824
 
1824
1825
  def on(
1825
- self, event_type: str, callback: Callable[[dict[str, Any], str, str], Any]
1826
+ self,
1827
+ event_type: str,
1828
+ callback: Callable[[dict[str, Any], str, str], Any],
1829
+ label: str | None = None,
1826
1830
  ) -> InlineWidget:
1827
1831
  """Register a callback for events from JavaScript.
1828
1832
 
@@ -1832,6 +1836,9 @@ class InlineWidget(GridStateMixin, PlotlyStateMixin, ToolbarStateMixin):
1832
1836
  Event name (e.g., 'plotly:click', 'toggle', 'grid:cell-click').
1833
1837
  callback : Callable[[dict[str, Any], str, str], Any]
1834
1838
  Handler function receiving (data, event_type, label).
1839
+ label : str or None, optional
1840
+ Ignored for inline widgets (accepted for API compatibility with
1841
+ ``PyWry.on()``).
1835
1842
 
1836
1843
  Returns
1837
1844
  -------
@@ -3773,6 +3780,186 @@ def _preload_chart_data(user_id: str = "default") -> dict[str, str]:
3773
3780
  return preload
3774
3781
 
3775
3782
 
3783
+ def generate_tvchart_html(
3784
+ chart_html: str,
3785
+ config_payload: str,
3786
+ chart_id: str,
3787
+ widget_id: str,
3788
+ title: str = "Chart",
3789
+ theme: ThemeLiteral | None = None,
3790
+ toolbars: list[dict[str, Any] | Toolbar] | None = None,
3791
+ modals: list[dict[str, Any] | Modal] | None = None,
3792
+ inline_css: str = "",
3793
+ full_document: bool = True,
3794
+ token: str | None = None,
3795
+ ) -> str:
3796
+ """Generate HTML for a TradingView Lightweight Chart.
3797
+
3798
+ Parameters
3799
+ ----------
3800
+ chart_html : str
3801
+ The chart container ``<div>`` (and any toolbar/modal markup).
3802
+ config_payload : str
3803
+ JSON string with ``chartOptions``, ``series``, ``storage``, etc.
3804
+ chart_id : str
3805
+ DOM id of the chart container element.
3806
+ widget_id : str
3807
+ Unique widget identifier (used by the pywry bridge).
3808
+ title : str
3809
+ Page title.
3810
+ theme : 'dark' or 'light', optional
3811
+ Color theme.
3812
+ toolbars : list, optional
3813
+ Toolbar configurations.
3814
+ modals : list, optional
3815
+ Modal configurations.
3816
+ inline_css : str
3817
+ Extra CSS to inject.
3818
+ full_document : bool
3819
+ If True, return complete HTML document; if False, content fragment only.
3820
+ token : str or None
3821
+ Widget auth token for the pywry bridge.
3822
+
3823
+ Returns
3824
+ -------
3825
+ str
3826
+ """
3827
+ from .assets import (
3828
+ get_pywry_css,
3829
+ get_scrollbar_js,
3830
+ get_toast_css,
3831
+ get_tvchart_defaults_js,
3832
+ get_tvchart_js,
3833
+ )
3834
+ from .modal import wrap_content_with_modals
3835
+ from .notebook import _wrap_content_with_toolbars
3836
+
3837
+ if theme is None:
3838
+ theme = _get_default_theme()
3839
+
3840
+ tvchart_js = get_tvchart_js()
3841
+ tvchart_script = f"<script>{tvchart_js}</script>" if tvchart_js else ""
3842
+ tvchart_defaults = get_tvchart_defaults_js()
3843
+ tvchart_defaults_script = f"<script>{tvchart_defaults}</script>" if tvchart_defaults else ""
3844
+
3845
+ # Chart init script — waits for LightweightCharts then renders
3846
+ chart_init_script = f"""<script>
3847
+ (function() {{
3848
+ function initChart() {{
3849
+ if (typeof LightweightCharts === 'undefined') {{
3850
+ setTimeout(initChart, 50);
3851
+ return;
3852
+ }}
3853
+ var payload = {config_payload};
3854
+ var container = document.getElementById('{chart_id}');
3855
+ if (!container) {{
3856
+ setTimeout(initChart, 50);
3857
+ return;
3858
+ }}
3859
+ if (window.PYWRY_TVCHART_RENDER) {{
3860
+ window.PYWRY_TVCHART_RENDER('{chart_id}', container, payload);
3861
+ }} else if (window.PYWRY_TVCHART_CREATE) {{
3862
+ window.PYWRY_TVCHART_CREATE('{chart_id}', container, payload);
3863
+ }}
3864
+ }}
3865
+ initChart();
3866
+ }})();
3867
+ </script>"""
3868
+
3869
+ if not full_document:
3870
+ # Content fragment for anywidget — caller handles wrapping
3871
+ wrapped = _wrap_content_with_toolbars(chart_html, toolbars)
3872
+ if modals:
3873
+ modal_html, modal_scripts = wrap_content_with_modals("", modals)
3874
+ wrapped = f"{wrapped}{modal_html}{modal_scripts}"
3875
+ return f"{wrapped}\n{chart_init_script}"
3876
+
3877
+ # Full document for IFrame / browser mode
3878
+ pywry_css = get_pywry_css()
3879
+ pywry_style = f"<style>{pywry_css}</style>" if pywry_css else ""
3880
+ toast_css = get_toast_css()
3881
+ toast_style = f"<style>{toast_css}</style>" if toast_css else ""
3882
+ scrollbar_js = get_scrollbar_js()
3883
+ scrollbar_script = f"<script>{scrollbar_js}</script>" if scrollbar_js else ""
3884
+ inline_style = f"<style>{inline_css}</style>" if inline_css else ""
3885
+
3886
+ if theme == "dark":
3887
+ widget_theme_class = "pywry-theme-dark"
3888
+ elif theme == "system":
3889
+ widget_theme_class = "pywry-theme-system"
3890
+ else:
3891
+ widget_theme_class = "pywry-theme-light"
3892
+
3893
+ # Build widget content with toolbars
3894
+ widget_content = wrap_content_with_toolbars(chart_html, toolbars)
3895
+
3896
+ # Inject modals
3897
+ modal_block = ""
3898
+ if modals:
3899
+ modal_html, modal_scripts = wrap_content_with_modals("", modals)
3900
+ modal_block = f"{modal_html}{modal_scripts}"
3901
+
3902
+ return f"""<!DOCTYPE html>
3903
+ <html class="{theme}">
3904
+ <head>
3905
+ <meta charset="utf-8">
3906
+ <title>{title}</title>
3907
+ {tvchart_script}
3908
+ {tvchart_defaults_script}
3909
+ {pywry_style}
3910
+ {toast_style}
3911
+ {inline_style}
3912
+ {scrollbar_script}
3913
+ <style>
3914
+ html, body {{
3915
+ margin: 0;
3916
+ padding: 0;
3917
+ width: 100%;
3918
+ height: 100%;
3919
+ overflow: hidden;
3920
+ background: var(--pywry-bg-primary);
3921
+ }}
3922
+ .pywry-widget {{
3923
+ --pywry-widget-width: 100%;
3924
+ --pywry-widget-height: 100%;
3925
+ width: 100%;
3926
+ height: 100%;
3927
+ display: flex;
3928
+ flex-direction: column;
3929
+ border: none;
3930
+ border-radius: 0;
3931
+ box-sizing: border-box;
3932
+ background-color: var(--pywry-bg-primary);
3933
+ }}
3934
+ .pywry-toolbar {{
3935
+ border: none;
3936
+ }}
3937
+ .pywry-content {{
3938
+ flex: 1;
3939
+ min-height: 0;
3940
+ box-sizing: border-box;
3941
+ overflow: hidden;
3942
+ }}
3943
+ .pywry-tvchart-container {{
3944
+ flex: 1;
3945
+ min-height: 0;
3946
+ width: 100%;
3947
+ height: 100%;
3948
+ box-sizing: border-box;
3949
+ }}
3950
+ </style>
3951
+ </head>
3952
+ <body>
3953
+ <div class="pywry-widget pywry-custom-scrollbar {widget_theme_class}">
3954
+ {widget_content}
3955
+ </div>
3956
+ {modal_block}
3957
+ {_get_pywry_bridge_js(widget_id, token)}
3958
+ {chart_init_script}
3959
+ </body>
3960
+ </html>"""
3961
+
3962
+
3776
3963
  def show_tvchart(
3777
3964
  data: Any = None,
3778
3965
  callbacks: dict[str, Callable[..., Any]] | None = None,
@@ -3845,10 +4032,8 @@ def show_tvchart(
3845
4032
  import json as _json
3846
4033
  import uuid as _uuid
3847
4034
 
3848
- from .modal import wrap_content_with_modals
3849
- from .notebook import _wrap_content_with_toolbars
4035
+ from .notebook import create_tvchart_widget
3850
4036
  from .runtime import is_headless
3851
- from .widget import HAS_ANYWIDGET, PyWryTVChartWidget
3852
4037
 
3853
4038
  if theme is None:
3854
4039
  theme = _get_default_theme()
@@ -3930,25 +4115,21 @@ def show_tvchart(
3930
4115
 
3931
4116
  chart_html = f'<div id="{chart_id}" class="pywry-tvchart-container"></div>'
3932
4117
 
3933
- # Inject toolbars
3934
- chart_html = _wrap_content_with_toolbars(chart_html, toolbars)
3935
-
3936
- # Inject modals
3937
- if modals:
3938
- modal_html, modal_scripts = wrap_content_with_modals("", modals)
3939
- chart_html = f"{chart_html}{modal_html}{modal_scripts}"
3940
-
3941
- if HAS_ANYWIDGET and not open_browser and not is_headless():
3942
- widget = PyWryTVChartWidget(
3943
- content=chart_html,
3944
- chart_config=config_payload,
3945
- theme=theme,
3946
- width=width,
3947
- height=f"{height}px",
3948
- chart_id=chart_id,
3949
- )
3950
- else:
3951
- widget = PyWryTVChartWidget(content=chart_html)
4118
+ # Create widget using auto-backend selection
4119
+ # Force InlineWidget (IFrame) for BROWSER mode since it has open_in_browser()
4120
+ widget = create_tvchart_widget(
4121
+ chart_html=chart_html,
4122
+ config_payload=config_payload,
4123
+ chart_id=chart_id,
4124
+ widget_id=widget_id,
4125
+ title=title,
4126
+ theme=theme,
4127
+ width=width,
4128
+ height=height,
4129
+ toolbars=toolbars,
4130
+ modals=modals,
4131
+ force_iframe=open_browser,
4132
+ )
3952
4133
 
3953
4134
  if callbacks:
3954
4135
  for event_type, callback in callbacks.items():
@@ -3960,14 +4141,17 @@ def show_tvchart(
3960
4141
  wire_storage(user_id="default")
3961
4142
 
3962
4143
  if provider is not None:
3963
- widget._wire_datafeed_provider(provider)
4144
+ wire_datafeed = getattr(widget, "_wire_datafeed_provider", None)
4145
+ if callable(wire_datafeed):
4146
+ wire_datafeed(provider)
3964
4147
 
4148
+ # Display
3965
4149
  if is_headless():
3966
4150
  pass
4151
+ elif open_browser:
4152
+ open_fn = getattr(widget, "open_in_browser", None)
4153
+ if callable(open_fn):
4154
+ open_fn()
3967
4155
  else:
3968
- open_in_browser = getattr(widget, "open_in_browser", None)
3969
- if open_browser and callable(open_in_browser):
3970
- open_in_browser()
3971
- else:
3972
- widget.display()
4156
+ widget.display()
3973
4157
  return widget
@@ -616,3 +616,111 @@ def create_dataframe_widget(
616
616
  widget.on("grid:export-csv", _make_grid_export_handler(widget))
617
617
 
618
618
  return widget
619
+
620
+
621
+ def create_tvchart_widget(
622
+ chart_html: str,
623
+ config_payload: str,
624
+ chart_id: str,
625
+ widget_id: str,
626
+ title: str = "Chart",
627
+ theme: Literal["dark", "light", "system"] = "dark",
628
+ width: str = "100%",
629
+ height: int = 500,
630
+ toolbars: list[Any] | None = None,
631
+ modals: list[Any] | None = None,
632
+ inline_css: str = "",
633
+ port: int | None = None,
634
+ force_iframe: bool = False,
635
+ ) -> Any:
636
+ """Create a TVChart widget using the best available backend.
637
+
638
+ Automatically selects:
639
+ 1. PyWryTVChartWidget (anywidget) if available - best performance
640
+ 2. InlineWidget (FastAPI) as fallback - broader compatibility
641
+
642
+ Parameters
643
+ ----------
644
+ chart_html : str
645
+ Chart container ``<div>`` HTML.
646
+ config_payload : str
647
+ JSON string with chart configuration.
648
+ chart_id : str
649
+ DOM id of the chart container element.
650
+ widget_id : str
651
+ Unique widget identifier.
652
+ title : str
653
+ Widget title.
654
+ theme : str
655
+ 'dark', 'light', or 'system'.
656
+ width : str
657
+ Widget width (CSS).
658
+ height : int
659
+ Widget height in pixels.
660
+ toolbars : list, optional
661
+ Toolbar configurations.
662
+ modals : list, optional
663
+ Modal configurations.
664
+ inline_css : str
665
+ Extra CSS to inject.
666
+ port : int, optional
667
+ Server port (only for InlineWidget fallback).
668
+ force_iframe : bool, optional
669
+ If True, force use of InlineWidget instead of anywidget.
670
+ Required for BROWSER mode which needs open_in_browser() method.
671
+ Default: False.
672
+
673
+ Returns
674
+ -------
675
+ BaseWidget
676
+ Widget instance implementing BaseWidget protocol.
677
+ """
678
+ from . import inline
679
+ from .modal import wrap_content_with_modals
680
+ from .runtime import is_headless
681
+ from .widget import HAS_ANYWIDGET
682
+
683
+ use_anywidget = HAS_ANYWIDGET and not force_iframe and not is_headless()
684
+ if use_anywidget:
685
+ from .widget import PyWryTVChartWidget
686
+
687
+ content = _wrap_content_with_toolbars(chart_html, toolbars)
688
+ if modals:
689
+ modal_html, modal_scripts = wrap_content_with_modals("", modals)
690
+ content = f"{content}{modal_html}{modal_scripts}"
691
+
692
+ return PyWryTVChartWidget(
693
+ content=content,
694
+ chart_config=config_payload,
695
+ theme=theme,
696
+ width=width,
697
+ height=f"{height}px" if isinstance(height, int) else height,
698
+ chart_id=chart_id,
699
+ )
700
+
701
+ # Fallback to InlineWidget (FastAPI server)
702
+ widget_token = inline._generate_widget_token(widget_id)
703
+
704
+ html = inline.generate_tvchart_html(
705
+ chart_html=chart_html,
706
+ config_payload=config_payload,
707
+ chart_id=chart_id,
708
+ widget_id=widget_id,
709
+ title=title,
710
+ theme=theme,
711
+ toolbars=toolbars,
712
+ modals=modals,
713
+ inline_css=inline_css,
714
+ full_document=True,
715
+ token=widget_token,
716
+ )
717
+
718
+ return inline.InlineWidget(
719
+ html=html,
720
+ width=width,
721
+ height=height,
722
+ port=port or 8765,
723
+ widget_id=widget_id,
724
+ browser_only=force_iframe,
725
+ token=widget_token,
726
+ )
@@ -46,6 +46,30 @@ def _check_redis() -> None:
46
46
  raise ImportError(msg)
47
47
 
48
48
 
49
+ def _to_str(value: Any) -> Any:
50
+ """Decode bytes → str, leave everything else unchanged."""
51
+ if isinstance(value, bytes):
52
+ try:
53
+ return value.decode("utf-8")
54
+ except UnicodeDecodeError:
55
+ return value
56
+ return value
57
+
58
+
59
+ def _decode_hash(data: Any) -> dict[str, Any]:
60
+ """Normalise a Redis hash mapping to ``dict[str, str]``."""
61
+ if not data:
62
+ return {}
63
+ return {_to_str(k): _to_str(v) for k, v in data.items()}
64
+
65
+
66
+ def _decode_set(data: Any) -> set[str]:
67
+ """Normalise a Redis set reply to ``set[str]``."""
68
+ if not data:
69
+ return set()
70
+ return {_to_str(m) for m in data}
71
+
72
+
49
73
  class RedisWidgetStore(WidgetStore):
50
74
  """Redis-backed widget store for horizontal scaling.
51
75
 
@@ -136,7 +160,7 @@ class RedisWidgetStore(WidgetStore):
136
160
  async def get(self, widget_id: str) -> WidgetData | None:
137
161
  """Get complete widget data."""
138
162
  r = await self._redis()
139
- data = await r.hgetall(self._widget_key(widget_id))
163
+ data = _decode_hash(await r.hgetall(self._widget_key(widget_id)))
140
164
 
141
165
  if not data:
142
166
  return None
@@ -403,7 +427,7 @@ class RedisConnectionRouter(ConnectionRouter):
403
427
  async def get_connection_info(self, widget_id: str) -> ConnectionInfo | None:
404
428
  """Get connection information for a widget."""
405
429
  r = await self._redis()
406
- data = await r.hgetall(self._conn_key(widget_id))
430
+ data = _decode_hash(await r.hgetall(self._conn_key(widget_id)))
407
431
 
408
432
  if not data:
409
433
  return None
@@ -559,7 +583,7 @@ class RedisSessionStore(SessionStore):
559
583
  async def get_session(self, session_id: str) -> UserSession | None:
560
584
  """Get a session by ID."""
561
585
  r = await self._redis()
562
- data = await r.hgetall(self._session_key(session_id))
586
+ data = _decode_hash(await r.hgetall(self._session_key(session_id)))
563
587
 
564
588
  if not data:
565
589
  return None
@@ -617,7 +641,7 @@ class RedisSessionStore(SessionStore):
617
641
  r = await self._redis()
618
642
  key = self._session_key(session_id)
619
643
 
620
- data = await r.hgetall(key)
644
+ data = _decode_hash(await r.hgetall(key))
621
645
  if not data:
622
646
  return False
623
647
 
@@ -779,7 +803,7 @@ class RedisChatStore(ChatStore):
779
803
  from pywry.chat import ChatThread
780
804
 
781
805
  r = await self._redis()
782
- data = await r.hgetall(self._thread_key(widget_id, thread_id))
806
+ data = _decode_hash(await r.hgetall(self._thread_key(widget_id, thread_id)))
783
807
  if not data:
784
808
  return None
785
809
 
@@ -977,7 +1001,8 @@ class RedisChartStore(ChartStore):
977
1001
  )
978
1002
  results: list[dict[str, Any]] = []
979
1003
  for lid, score in ids_with_scores:
980
- meta = await r.hgetall(self._meta_key(user_id, lid))
1004
+ s_lid = _to_str(lid)
1005
+ meta = _decode_hash(await r.hgetall(self._meta_key(user_id, s_lid)))
981
1006
  if meta:
982
1007
  meta["savedAt"] = int(float(meta.get("savedAt", score)))
983
1008
  results.append(meta)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes