Compare commits

..

2 Commits

Author SHA1 Message Date
Gud Boi c210c286a5 Use walrus `getattr()` over `hasattr()` in `_window`
Replace all nested `hasattr()` + re-access chains
with `:= getattr(..., None)` walrus assigns
throughout the zoom UI methods; flattens deeply
nested `if hasattr` / `if hasattr` / `if hasattr`
pyramids into single chained `and` conditions.

Also,
- apply multiline code style per `py-codestyle`
  (list literals, fn sigs, `except` clauses,
  comments, docstrings)
- replace bare `pass` in `except` handlers with
  `log.exception()` calls
- fix `_qt_win` annotation to `QMainWindow|None`

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-07 15:19:04 -04:00
Gud Boi 12b015cec4 Tighten logging and annotations in `_web_bs`
Split multi-value log msgs onto separate f-str
lines, add `!r` to URL and error format refs,
and fix `response_type` annotation from bare
`type` to `Type[Struct]`.

Also,
- Use `X|Y` union style (no spaces).
- Add `-> None` return hint to `proxy_msgs()`.
- Single backticks in `fixture` docstring ref.
- Expand `Callable` return type across lines.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-07 14:58:37 -04:00
2 changed files with 125 additions and 58 deletions

View File

@ -168,7 +168,7 @@ async def _reconnect_forever(
nobsws: NoBsWs,
reset_after: int, # msg recv timeout before reset attempt
fixture: AsyncContextManager | None = None,
fixture: AsyncContextManager|None = None,
task_status: TaskStatus = trio.TASK_STATUS_IGNORED,
) -> None:
@ -185,7 +185,7 @@ async def _reconnect_forever(
async def proxy_msgs(
ws: WebSocketConnection,
rent_cs: trio.CancelScope, # parent cancel scope
):
) -> None:
'''
Receive (under `timeout` deadline) all msgs from from underlying
websocket and relay them to (calling) parent task via ``trio``
@ -206,7 +206,7 @@ async def _reconnect_forever(
except nobsws.recon_errors:
log.exception(
f'{src_mod}\n'
f'{url} connection bail with:'
f'{url!r} connection failed\n'
)
with trio.CancelScope(shield=True):
await trio.sleep(0.5)
@ -269,7 +269,7 @@ async def _reconnect_forever(
nobsws._ws = ws
log.info(
f'{src_mod}\n'
f'Connection success: {url}'
f'Connection success: {url!r}'
)
# begin relay loop to forward msgs
@ -341,7 +341,7 @@ async def _reconnect_forever(
async def open_autorecon_ws(
url: str,
fixture: AsyncContextManager | None = None,
fixture: AsyncContextManager|None = None,
# time in sec between msgs received before
# we presume connection might need a reset.
@ -361,7 +361,7 @@ async def open_autorecon_ws(
and restarts the full http(s) handshake on catches of certain
connetivity errors, or some user defined recv timeout.
You can provide a ``fixture`` async-context-manager which will be
You can provide a `fixture` async-context-manager which will be
entered/exitted around each connection reset; eg. for
(re)requesting subscriptions without requiring streaming setup
code to rerun.
@ -402,7 +402,8 @@ async def open_autorecon_ws(
except NoBsWs.recon_errors as con_err:
log.warning(
f'Entire ws-channel disconnect due to,\n'
f'con_err: {con_err!r}\n'
f'\n'
f'{con_err!r}\n'
)
@ -424,7 +425,7 @@ class JSONRPCResult(Struct):
async def open_jsonrpc_session(
url: str,
start_id: int = 0,
response_type: type = JSONRPCResult,
response_type: Type[Struct] = JSONRPCResult,
msg_recv_timeout: float = float('inf'),
# ^NOTE, since only `deribit` is using this jsonrpc stuff atm
# and options mkts are generally "slow moving"..
@ -435,7 +436,10 @@ async def open_jsonrpc_session(
# broken and never restored with wtv init sequence is required to
# re-establish a working req-resp session.
) -> Callable[[str, dict], dict]:
) -> Callable[
[str, dict],
dict,
]:
'''
Init a json-RPC-over-websocket connection to the provided `url`.
@ -531,14 +535,18 @@ async def open_jsonrpc_session(
'id': mid,
} if not rpc_results.get(mid):
log.warning(
f'Unexpected ws msg: {json.dumps(msg, indent=4)}'
f'Unexpected ws msg?\n'
f'{json.dumps(msg, indent=4)}'
)
case {
'method': _,
'params': _,
}:
log.debug(f'Recieved\n{msg}')
log.debug(
f'Recieved\n'
f'{msg!r}'
)
case {
'error': error
@ -554,12 +562,15 @@ async def open_jsonrpc_session(
result['event'].set()
log.error(
f'JSONRPC request failed\n'
f'req: {req_msg}\n'
f'resp: {error}\n'
f'req: {req_msg!r}\n'
f'resp: {error!r}\n'
)
case _:
log.warning(f'Unhandled JSON-RPC msg!?\n{msg}')
log.warning(
f'Unhandled JSON-RPC msg!?\n'
f'{msg!r}'
)
tn.start_soon(recv_task)
yield json_rpc

View File

@ -342,7 +342,10 @@ class MainWindow(QMainWindow):
log.debug('Saved window geometry for next session')
# raising KBI seems to get intercepted by by Qt so just use the system.
os.kill(os.getpid(), signal.SIGINT)
os.kill(
os.getpid(),
signal.SIGINT,
)
@property
def status_bar(self) -> QStatusBar:
@ -549,9 +552,9 @@ class MainWindow(QMainWindow):
# update godwidget and its children
if self.godwidget:
# update search widget if it exists
if hasattr(self.godwidget, 'search') and self.godwidget.search:
self.godwidget.search.update_fonts()
# update search bg if it exists
if search := getattr(self.godwidget, 'search', None):
search.update_fonts()
# update order mode panes in all chart views
self._update_chart_order_panes()
@ -571,7 +574,10 @@ class MainWindow(QMainWindow):
return
# iterate through all linked splits (hist and rt)
for splits_name in ['hist_linked', 'rt_linked']:
for splits_name in [
'hist_linked',
'rt_linked',
]:
splits = getattr(self.godwidget, splits_name, None)
if not splits:
continue
@ -583,12 +589,14 @@ class MainWindow(QMainWindow):
self._update_chart_axes(chart)
# update order pane
if hasattr(chart, 'view'):
view = chart.view
if hasattr(view, 'order_mode') and view.order_mode:
order_mode = view.order_mode
if hasattr(order_mode, 'pane') and order_mode.pane:
order_mode.pane.update_fonts()
if (
(view := getattr(chart, 'view', None))
and
(order_mode := getattr(view, 'order_mode', None))
and
(pane := getattr(order_mode, 'pane', None))
):
pane.update_fonts()
# also check subplots
subplots = getattr(splits, 'subplots', {})
@ -597,54 +605,90 @@ class MainWindow(QMainWindow):
self._update_chart_axes(subplot_chart)
# update subplot order pane
if hasattr(subplot_chart, 'view'):
subplot_view = subplot_chart.view
if hasattr(subplot_view, 'order_mode') and subplot_view.order_mode:
subplot_order_mode = subplot_view.order_mode
if hasattr(subplot_order_mode, 'pane') and subplot_order_mode.pane:
subplot_order_mode.pane.update_fonts()
if (
(view := getattr(subplot_chart, 'view', None))
and
(order_mode := getattr(
view, 'order_mode', None,
))
and
(pane := getattr(order_mode, 'pane', None))
):
pane.update_fonts()
# resize all sidepanes to match main chart's sidepane width
# this ensures volume/subplot sidepanes match the main chart
if splits and hasattr(splits, 'resize_sidepanes'):
splits.resize_sidepanes()
# resize all sidepanes to match the
# main chart's sidepane width; ensures
# volume/subplot sidepanes match.
if (
splits
and
(resize := getattr(
splits, 'resize_sidepanes', None,
))
):
resize()
def _update_chart_axes(self, chart) -> None:
'''Update axis fonts and sizing for a chart.'''
'''
Update axis fonts and sizing for a chart.
'''
from . import _style
# update price axis (right side)
if hasattr(chart, 'pi') and chart.pi:
plot_item = chart.pi
if plot_item := getattr(chart, 'pi', None):
# get all axes from plot item
for axis_name in ['left', 'right', 'bottom', 'top']:
axis = plot_item.getAxis(axis_name)
if axis and hasattr(axis, 'update_fonts'):
axis.update_fonts(_style._font)
for axis_name in [
'left',
'right',
'bottom',
'top',
]:
if (
(axis := plot_item.getAxis(axis_name))
and
(update_fonts := getattr(
axis, 'update_fonts', None,
))
):
update_fonts(_style._font)
# force plot item to recalculate its entire layout
# force plot item to recalculate
# its entire layout
plot_item.updateGeometry()
# force chart widget to update
if hasattr(chart, 'updateGeometry'):
chart.updateGeometry()
if update_geom := getattr(chart, 'updateGeometry', None):
update_geom()
# trigger a full scene update
if hasattr(chart, 'update'):
chart.update()
if update := getattr(chart, 'update', None):
update()
def _refresh_widget_fonts(self, widget: QWidget) -> None:
def _refresh_widget_fonts(
self,
widget: QWidget,
) -> None:
'''
Recursively update font sizes in all child widgets.
Recursively update font sizes in all
child widgets.
Handles widgets that have font-size
hardcoded in their stylesheets.
This handles widgets that have font-size hardcoded in their stylesheets.
'''
from . import _style
# recursively process all children
for child in widget.findChildren(QWidget):
# skip widgets that have their own update_fonts method (handled separately)
if hasattr(child, 'update_fonts'):
# skip widgets that have custom update
# methods; handled separately below.
if getattr(child, 'update_fonts', None):
log.debug(
f'Skipping sub-widget with'
f' custom font-update meth..\n'
f'{child!r}\n'
)
continue
# update child's stylesheet if it has font-size
@ -654,23 +698,35 @@ class MainWindow(QMainWindow):
# this is a heuristic - may need refinement
try:
child.setFont(_style._font.font)
except (AttributeError, RuntimeError):
pass
except (
AttributeError,
RuntimeError,
):
log.exception(
'Failed to update sub-widget font?\n'
)
# update child's font
try:
child.setFont(_style._font.font)
except (AttributeError, RuntimeError):
pass
except (
AttributeError,
RuntimeError,
):
log.exception(
'Failed to update sub-widget font?\n'
)
# singleton app per actor
_qt_win: QMainWindow = None
_qt_win: QMainWindow|None = None
def main_window() -> MainWindow:
'Return the actor-global Qt window.'
'''
Return the actor-global Qt window.
'''
global _qt_win
assert _qt_win
return _qt_win