Skip to content

Commit d0f89af

Browse files
author
deeleeramone
committed
fix some inline tv issues
1 parent 5e143b9 commit d0f89af

28 files changed

Lines changed: 1368 additions & 700 deletions

pywry/examples/pywry_demo_aggrid.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@
331331
"# Single row selection (no checkboxes)\n",
332332
"single_select = RowSelection(\n",
333333
" mode='singleRow',\n",
334-
" checkbox_selection=False,\n",
334+
" checkboxes=False,\n",
335335
" enable_click_selection=True,\n",
336336
")\n",
337337
"\n",

pywry/examples/pywry_demo_deploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ def create_widget(self) -> str:
483483
</div>
484484
485485
<p class="demo-footer">
486-
Built with <a href="https://github.com/OpenBB-finance/OpenBB">PyWry 2.0</a>
486+
Built with <a href="https://github.com/deeleeramone/PyWry">PyWry 2.0</a>
487487
</p>
488488
</div>
489489
"""

pywry/examples/pywry_demo_tvchart.ipynb

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,58 @@
9393
" toolbars=build_tvchart_toolbars(),\n",
9494
")"
9595
]
96+
},
97+
{
98+
"cell_type": "markdown",
99+
"id": "b5177b48",
100+
"metadata": {},
101+
"source": [
102+
"## UDF Adapter"
103+
]
104+
},
105+
{
106+
"cell_type": "code",
107+
"execution_count": null,
108+
"id": "0f4b0ae0",
109+
"metadata": {},
110+
"outputs": [],
111+
"source": [
112+
"from pywry import PyWry\n",
113+
"from pywry.tvchart.udf import UDFAdapter\n",
114+
"\n",
115+
"\n",
116+
"# BitMEX UDF Adapter for TradingView\n",
117+
"UDF_URL = \"https://www.bitmex.com/api/udf\"\n",
118+
"\n",
119+
"app = PyWry()\n",
120+
"\n",
121+
"udf = UDFAdapter(UDF_URL, poll_interval=60)\n",
122+
"\n",
123+
"handle = udf.connect(\n",
124+
" app,\n",
125+
" symbol=\"XBTUSD\",\n",
126+
" resolution=\"D\",\n",
127+
")\n"
128+
]
96129
}
97130
],
98131
"metadata": {
132+
"kernelspec": {
133+
"display_name": "python",
134+
"language": "python",
135+
"name": "python3"
136+
},
99137
"language_info": {
100-
"name": "python"
138+
"codemirror_mode": {
139+
"name": "ipython",
140+
"version": 3
141+
},
142+
"file_extension": ".py",
143+
"mimetype": "text/x-python",
144+
"name": "python",
145+
"nbconvert_exporter": "python",
146+
"pygments_lexer": "ipython3",
147+
"version": "3.13.11"
101148
}
102149
},
103150
"nbformat": 4,

pywry/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pywry"
3-
version = "2.0.0rc5"
3+
version = "2.0.0rc6"
44
description = "A lightweight and blazingly fast, cross-platform, WebView rendering engine and desktop UI toolkit for Python."
55
authors = [{ name = "PyWry", email = "pywry2@gmail.com" }]
66
license = { text = "Apache 2.0" }

pywry/pywry/app.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from .assets import (
1717
get_aggrid_css,
1818
get_aggrid_js,
19-
get_openbb_icon,
2019
get_plotly_js,
20+
get_pywry_icon,
2121
)
2222
from .callbacks import CallbackFunc, get_registry
2323
from .config import PyWrySettings
@@ -448,7 +448,7 @@ def _show_login_page_and_wait(self, provider: Any, page_title: str) -> None:
448448

449449
click_event = threading.Event()
450450

451-
def _on_click(data: Any) -> None:
451+
def _on_click(data: Any) -> None: # pylint: disable=unused-argument
452452
click_event.set()
453453

454454
self.show(
@@ -478,7 +478,7 @@ def _wire_logout_handler(
478478
"""
479479
registry = get_registry()
480480

481-
def _handle_logout(data: Any, event_type: str, label: str) -> None:
481+
def _handle_logout(data: Any, event_type: str, label: str) -> None: # pylint: disable=unused-argument
482482
# Call developer's on_logout middleware
483483
if on_logout is not None:
484484
on_logout()
@@ -1434,9 +1434,47 @@ def show_tvchart(
14341434
if provider is not None:
14351435
use_datafeed = True
14361436

1437-
series_payload: list[dict[str, Any]] = []
1437+
# In notebook/browser mode, delegate to inline.show_tvchart() which
1438+
# creates a PyWryTVChartWidget with the LightweightCharts library
1439+
# bundled in the ESM. The generic inline.show() path does not include
1440+
# tvchart assets, so the chart would never initialise.
1441+
is_browser_mode = isinstance(self._mode, BrowserMode)
1442+
if should_use_inline_rendering() or is_browser_mode:
1443+
from . import inline as pywry_inline
1444+
1445+
plain_callbacks: dict[str, Any] | None = None
1446+
if callbacks:
1447+
plain_callbacks = {
1448+
event: (cb.func if hasattr(cb, "func") else cb)
1449+
for event, cb in callbacks.items()
1450+
}
1451+
1452+
widget_width = "100%" if width is None else f"{width}px"
1453+
1454+
widget = pywry_inline.show_tvchart(
1455+
data=data,
1456+
callbacks=plain_callbacks,
1457+
title=title or "Chart",
1458+
width=widget_width,
1459+
height=height or 700,
1460+
theme="dark" if self._theme == ThemeMode.DARK else "light",
1461+
chart_options=chart_options,
1462+
series_options=series_options,
1463+
symbol_col=symbol_col,
1464+
max_bars=max_bars,
1465+
toolbars=toolbars,
1466+
modals=modals,
1467+
open_browser=is_browser_mode,
1468+
storage=storage,
1469+
use_datafeed=use_datafeed,
1470+
symbol=symbol,
1471+
resolution=resolution,
1472+
provider=provider,
1473+
)
1474+
self._register_inline_widget(widget)
1475+
return widget # type: ignore[no-any-return]
14381476
if use_datafeed:
1439-
series_payload = [
1477+
series_payload: list[dict[str, Any]] = [
14401478
{
14411479
"seriesId": "main",
14421480
"symbol": symbol or "",
@@ -1452,16 +1490,17 @@ def show_tvchart(
14521490

14531491
chart_data = normalize_ohlcv(data, symbol_col=symbol_col, max_bars=max_bars)
14541492

1455-
for s in chart_data.series:
1456-
series_payload.append(
1457-
{
1458-
"seriesId": s.series_id,
1459-
"bars": s.bars,
1460-
"volume": s.volume,
1461-
"seriesType": s.series_type.value.capitalize(),
1462-
"seriesOptions": series_options or {},
1463-
}
1464-
)
1493+
series_payload = []
1494+
series_payload.extend(
1495+
{
1496+
"seriesId": s.series_id,
1497+
"bars": s.bars,
1498+
"volume": s.volume,
1499+
"seriesType": s.series_type.value.capitalize(),
1500+
"seriesOptions": series_options or {},
1501+
}
1502+
for s in chart_data.series
1503+
)
14651504

14661505
from .state import is_deploy_mode
14671506

@@ -2524,7 +2563,7 @@ def get_icon(self) -> bytes:
25242563
bytes
25252564
Icon bytes.
25262565
"""
2527-
return get_openbb_icon()
2566+
return get_pywry_icon()
25282567

25292568
def get_lifecycle(self) -> WindowLifecycle:
25302569
"""Get the window lifecycle manager.

pywry/pywry/assets.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ def _get_pywry_css_bundled() -> str:
218218

219219

220220
@lru_cache(maxsize=1)
221-
def get_openbb_icon() -> bytes:
222-
"""Get the bundled OpenBB icon.
221+
def get_pywry_icon() -> bytes:
222+
"""Get the bundled PyWry icon.
223223
224224
Returns
225225
-------
@@ -273,8 +273,8 @@ def get_aggrid_defaults_js() -> str:
273273
return ""
274274

275275

276-
def get_openbb_icon_path() -> Path | None:
277-
"""Get the path to the bundled OpenBB icon.
276+
def get_pywry_icon_path() -> Path | None:
277+
"""Get the path to the bundled PyWry icon.
278278
279279
Returns
280280
-------
@@ -294,7 +294,7 @@ def clear_cache() -> None:
294294
get_aggrid_css.cache_clear()
295295
get_aggrid_defaults_js.cache_clear()
296296
get_pywry_css.cache_clear()
297-
get_openbb_icon.cache_clear()
297+
get_pywry_icon.cache_clear()
298298
get_toast_notifications_js.cache_clear()
299299
get_toast_css.cache_clear()
300300
get_modal_handlers_js.cache_clear()

pywry/pywry/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def _load_toml_config() -> dict[str, Any]:
8888

8989
# Deep merge
9090
merged = _deep_merge(merged, data)
91-
except (OSError, TypeError, ValueError, toml_decode_error):
91+
except (OSError, TypeError, ValueError, toml_decode_error): # type: ignore[misc]
9292
pass # Silently ignore invalid config files
9393

9494
return merged

pywry/pywry/frontend/src/plotly-widget.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ if (!window.__pywryStripThemeColors) {
8282
function render({ model, el }) {
8383
el.innerHTML = '';
8484

85+
// Inject CSS into the main document to fix Jupyter output cell backgrounds
86+
// This must be done here because _css only applies inside the widget shadow DOM
87+
const JUPYTER_FIX_ID = 'pywry-jupyter-fix-css';
88+
if (!document.getElementById(JUPYTER_FIX_ID)) {
89+
const style = document.createElement('style');
90+
style.id = JUPYTER_FIX_ID;
91+
style.textContent = `
92+
.cell-output-ipywidget-background {
93+
background-color: transparent !important;
94+
}
95+
.jp-OutputArea-output {
96+
background-color: transparent !important;
97+
}
98+
`;
99+
document.head.appendChild(style);
100+
}
101+
85102
// Apply theme class to el (AnyWidget container) for proper theming
86103
// CSS rule on .pywry-theme-dark/.pywry-theme-light applies background-color
87104
const isDarkInitial = model.get('theme') === 'dark';

pywry/pywry/frontend/src/toolbar-handlers.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -471,9 +471,9 @@ function initToolbarHandlers(container, pywry) {
471471
toolbar.setAttribute('aria-expanded', !isCollapsed);
472472
var storageKey = 'pywry-toolbar-collapsed-' + toolbarId;
473473
sessionStorage.setItem(storageKey, isCollapsed);
474-
if (window.pywry && window.pywry.emit) {
474+
if (pywry && pywry.emit) {
475475
var eventName = isCollapsed ? 'toolbar:collapse' : 'toolbar:expand';
476-
window.pywry.emit(eventName, { componentId: toolbarId, collapsed: isCollapsed }, toolbar);
476+
pywry.emit(eventName, { componentId: toolbarId, collapsed: isCollapsed }, toolbar);
477477
}
478478
}
479479
});
@@ -523,8 +523,8 @@ function initToolbarHandlers(container, pywry) {
523523
var position = resizeState.position;
524524
if (position === 'left' || position === 'right') sessionStorage.setItem('pywry-toolbar-width-' + componentId, toolbar.style.width);
525525
if (position === 'top' || position === 'bottom') sessionStorage.setItem('pywry-toolbar-height-' + componentId, toolbar.style.height);
526-
if (window.pywry && window.pywry.emit) {
527-
window.pywry.emit('toolbar:resize', { componentId: componentId, position: position, width: toolbar.offsetWidth, height: toolbar.offsetHeight }, toolbar);
526+
if (pywry && pywry.emit) {
527+
pywry.emit('toolbar:resize', { componentId: componentId, position: position, width: toolbar.offsetWidth, height: toolbar.offsetHeight }, toolbar);
528528
}
529529
resizeState.active = false;
530530
resizeState.toolbar = null;

0 commit comments

Comments
 (0)