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.
- {pywry-2.0.2 → pywry-2.0.4}/PKG-INFO +1 -1
- {pywry-2.0.2 → pywry-2.0.4}/pyproject.toml +1 -1
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-grid-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-grid-community-35.0.0.min.js.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-alpine-dark-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-alpine-light-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-balham-dark-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-balham-light-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-material-dark-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-material-light-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-quartz-dark-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/ag-theme-quartz-light-35.0.0.css.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/lightweight-charts-5.1.0.standalone.production.js.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/plotly-3.3.1.js.gz +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/inline.py +214 -30
- {pywry-2.0.2 → pywry-2.0.4}/pywry/notebook.py +108 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/redis.py +31 -6
- {pywry-2.0.2 → pywry-2.0.4}/.gitignore +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/AGENTS.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/README.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/hatch_build.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/Tauri.toml +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/__main__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/.claude-plugin/marketplace.json +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/.claude-plugin/plugin.json +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/.mcp.json +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/CHANGELOG.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/PRIVACY.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/README.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/RELEASING.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/agents/pywry-builder.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/commands/doctor.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/commands/examples.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/commands/scaffold.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/hooks/hooks.json +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_claude_plugin/skills/pywry-orientation/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_freeze.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_pyinstaller_hook/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_pyinstaller_hook/hook-pywry.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/_vendor/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/app.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/asset_loader.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/assets.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/callback_server.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/deploy_routes.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/flow.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/login_page.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/pkce.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/providers.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/session.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/auth/token_store.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/callbacks.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/capabilities/default.toml +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/artifacts.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/html.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/manager.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/models.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/permissions.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/anthropic.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/callback.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/deepagent.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/magentic.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/openai.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/providers/stdio.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/session.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/chat/updates.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/cli.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/commands/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/commands/window_commands.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/config.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/exceptions.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry-dark.svg +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry-icon.svg +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry-light.svg +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/PyWry.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/icon.icns +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/icon.ico +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/icon.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/128x128.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/128x128@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/32x32.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/64x64.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square107x107Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square142x142Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square150x150Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square284x284Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square30x30Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square310x310Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square44x44Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square71x71Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/Square89x89Logo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/StoreLogo.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-hdpi/ic_launcher.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-hdpi/ic_launcher_round.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-mdpi/ic_launcher.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-mdpi/ic_launcher_round.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xhdpi/ic_launcher.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xhdpi/ic_launcher_round.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxhdpi/ic_launcher.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxxhdpi/ic_launcher.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/android/values/ic_launcher_background.xml +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/icon.icns +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/icon.ico +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/icon.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@1x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@2x-1.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-20x20@3x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@1x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@2x-1.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-29x29@3x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@1x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@2x-1.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-40x40@3x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-512@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-60x60@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-60x60@3x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-76x76@1x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-76x76@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/assets/tauri-icons/ios/AppIcon-83.5x83.5@2x.png +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/index.html +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/aggrid-defaults.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/auth-helpers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/bridge.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/chat-handlers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/cleanup.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/event-bridge.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/hot-reload.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/main.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/modal-handlers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/plotly-defaults.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/plotly-templates.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/plotly-widget.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/scrollbar.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/system-events.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/theme-manager.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/toast-notifications.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/toolbar-bridge.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/toolbar-handlers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tooltip-manager.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/01-globals.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/02-datafeed.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/03-theme.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/04-series.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/05-lifecycle.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/06-storage.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/00-state-tools.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/01-color-picker.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/02-helpers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/03-utils.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/04-settings-apply.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/05-hit-test.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/06-render.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/07-toolbar-menu.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/07-drawing/08-mouse-tools.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/00-state-helpers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/01-symbol-search.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/02-series-settings.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/03-volume-settings.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/04-chart-settings.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/05-compare.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/06-indicator-symbol-picker.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/08-settings/07-drawing-settings.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/00-helpers-catalog.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/01-compute-basic.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/02-bb-primitive.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/03-ichimoku-primitive.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/04-volume-profile.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/05-compute-extra.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/06-indicator-helpers.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/07-remove-legend.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/08-pane-management.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/09-legend-rebuild.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/10-settings-dialog.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/11-apply-settings.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/09-indicators/12-panel.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/10-events.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/11-legend.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart/12-session-tz.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/tvchart-widget.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/src/ws-bridge.js +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/chat.css +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/pywry.css +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/toast.css +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/frontend/style/tvchart.css +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/grid.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/hot_reload.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/log.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/__main__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/agentic.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/app_artifact.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/builders.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/docs.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/handlers.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/install.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/prompts.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/resources.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/server.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/authentication/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/autonomous_building/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/chat/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/chat_agent/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/component_reference/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/css_selectors/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/data_visualization/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/deploy/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/events/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/forms_and_inputs/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/iframe/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/interactive_buttons/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/jupyter/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/modals/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/native/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/styling/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/skills/tvchart/SKILL.md +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/state.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/mcp/tools.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/menu_proxy.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/modal.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/models.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/plotly_config.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/runtime.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/scripts.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/_factory.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/auth.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/base.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/callbacks.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/file.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/memory.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/server.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/sqlite.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/sync_helpers.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state/types.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/state_mixins.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/templates.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/toolbar.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tray_proxy.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/config.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/datafeed.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/mixin.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/models.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/normalize.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/toolbars.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/tvchart/udf.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/types.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/utils/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/utils/async_helpers.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/watcher.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/widget.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/widget_protocol.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_dispatch.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/controller.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/lifecycle.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/__init__.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/base.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/browser.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/multi_window.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/new_window.py +0 -0
- {pywry-2.0.2 → pywry-2.0.4}/pywry/window_manager/modes/single_window.py +0 -0
- {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.
|
|
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.
|
|
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" }
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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,
|
|
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 .
|
|
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
|
-
#
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|