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

View File

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