From 44c2c268f941999efa54f838b8c49f9d968561d7 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 12 Nov 2024 11:35:02 +0100 Subject: [PATCH] Implement env vars (#16) --- docs/backends.rst | 23 ++++++++++++++++++++++- docs/conf.py | 2 +- rendercanvas/auto.py | 28 ++++++++++++++++++++++------ tests/test_offscreen.py | 36 ++++++++++++++++++++++++++++++++++-- tests/testutils.py | 2 +- 5 files changed, 80 insertions(+), 11 deletions(-) diff --git a/docs/backends.rst b/docs/backends.rst index 13f8d8b..200844a 100644 --- a/docs/backends.rst +++ b/docs/backends.rst @@ -131,6 +131,27 @@ subclass implementing a remote frame-buffer. There are also some `wgpu examples canvas # Use as cell output + +.. _env_vars: + +Selecting a backend with env vars +--------------------------------- + +The automatic backend selection can be influenced with the use of environment +variables. This makes it possible to e.g. create examples using the +auto-backend, and allow these examples to run on CI with the offscreen backend. +Note that once ``rendercanvas.auto`` is imported, the selection has been made, +and importing it again always yields the same backend. + +* ``RENDERCANVAS_BACKEND``: Set the name of the backend that the auto-backend should select. Case insensituve. +* ``RENDERCANVAS_FORCE_OFFSCREEN``: force the auto-backend to select the offscreen canvas, ignoring the above env var. Truethy values are '1', 'true', and 'yes'. + +Rendercanvas also supports the following env vars for backwards compatibility, but only when the corresponding ``RENDERCANVAS_`` env var is unset or an empty string: + +* ``WGPU_GUI_BACKEND``: legacy alias. +* ``WGPU_FORCE_OFFSCREEN``: legacy alias. + + .. _interactive_use: Interactive use @@ -152,7 +173,7 @@ honor that and use Qt instead. On ``jupyter console`` and ``qtconsole``, the kernel is the same as in ``jupyter notebook``, making it (about) impossible to tell that we cannot actually use ipywidgets. So it will try to use ``jupyter_rfb``, but cannot render anything. -It's therefore advised to either use ``%gui qt`` or set the ``WGPU_GUI_BACKEND`` env var +It's therefore advised to either use ``%gui qt`` or set the ``RENDERCANVAS_BACKEND`` env var to "glfw". The latter option works well, because these kernels *do* have a running asyncio event loop! diff --git a/docs/conf.py b/docs/conf.py index f4b5ead..7a7d977 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ ROOT_DIR = os.path.abspath(os.path.join(__file__, "..", "..")) sys.path.insert(0, ROOT_DIR) -os.environ["WGPU_FORCE_OFFSCREEN"] = "true" +os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" # Load wglibu so autodoc can query docstrings diff --git a/rendercanvas/auto.py b/rendercanvas/auto.py index 02e414d..78da682 100644 --- a/rendercanvas/auto.py +++ b/rendercanvas/auto.py @@ -85,19 +85,35 @@ def backends_generator(): def backends_by_env_vars(): """Generate backend names set via one the supported environment variables.""" + + # We also support the legacy WGPU_X env vars, but only when the + # corresponding RENDERCANVAS_X is not set or set to the empty string. + + def get_env_var(*varnames): + for varname in varnames: + value = os.getenv(varname, "").lower() + if value: + return value, varname + else: + return "", varnames[0] + # Env var intended for testing, overrules everything else - if os.environ.get("WGPU_FORCE_OFFSCREEN", "").lower() in ("1", "true", "yes"): - yield "offscreen", "WGPU_FORCE_OFFSCREEN is set" + force_offscreen, varname = get_env_var( + "RENDERCANVAS_FORCE_OFFSCREEN", "WGPU_FORCE_OFFSCREEN" + ) + if force_offscreen and force_offscreen in ("1", "true", "yes"): + yield "offscreen", f"{varname} is set" + # Env var to force a backend for general use - backend_name = os.getenv("WGPU_GUI_BACKEND", "").lower().strip() or None + backend_name, varname = get_env_var("RENDERCANVAS_BACKEND", "WGPU_GUI_BACKEND") if backend_name: if backend_name not in BACKEND_NAMES: logger.warning( - f"Ignoring invalid WGPU_GUI_BACKEND '{backend_name}', must be one of {BACKEND_NAMES}" + f"Ignoring invalid {varname} '{backend_name}', must be one of {BACKEND_NAMES}" ) backend_name = None if backend_name: - yield backend_name, "WGPU_GUI_BACKEND is set" + yield backend_name, f"{varname} is set" def backends_by_jupyter(): @@ -116,7 +132,7 @@ def backends_by_jupyter(): # whether we're in a console or notebook. Technically this kernel could be # connected to a client of each. So we assume that ipywidgets can be used. # User on jupyter console (or similar) should ``%gui qt`` or set - # WGPU_GUI_BACKEND to 'glfw'. + # RENDERCANVAS_BACKEND to 'glfw'. # If GUI integration is enabled, we select the corresponding backend instead of jupyter app = getattr(ip.kernel, "app", None) diff --git a/tests/test_offscreen.py b/tests/test_offscreen.py index 75df1c8..0eee2da 100644 --- a/tests/test_offscreen.py +++ b/tests/test_offscreen.py @@ -12,7 +12,36 @@ def test_offscreen_selection_using_env_var(): from rendercanvas.offscreen import ManualOffscreenRenderCanvas - ori = os.environ.get("WGPU_FORCE_OFFSCREEN", "") + ori = os.getenv("RENDERCANVAS_FORCE_OFFSCREEN") + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "1" + + # We only need the func, but this triggers the auto-import + from rendercanvas.auto import select_backend + + try: + if not os.getenv("CI"): + for value in ["", "0", "false", "False", "wut"]: + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = value + module = select_backend() + assert module.RenderCanvas is not ManualOffscreenRenderCanvas + + for value in ["1", "true", "True"]: + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = value + module = select_backend() + assert module.RenderCanvas is ManualOffscreenRenderCanvas + + finally: + if ori is not None: + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = ori + + +def test_offscreen_selection_using_legacyt_env_var(): + from rendercanvas.offscreen import ManualOffscreenRenderCanvas + + ori1 = os.getenv("RENDERCANVAS_FORCE_OFFSCREEN") + ori2 = os.getenv("WGPU_FORCE_OFFSCREEN") + + os.environ.pop("RENDERCANVAS_FORCE_OFFSCREEN", None) os.environ["WGPU_FORCE_OFFSCREEN"] = "1" # We only need the func, but this triggers the auto-import @@ -31,7 +60,10 @@ def test_offscreen_selection_using_env_var(): assert module.RenderCanvas is ManualOffscreenRenderCanvas finally: - os.environ["WGPU_FORCE_OFFSCREEN"] = ori + if ori1 is not None: + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = ori1 + if ori2 is not None: + os.environ["WGPU_FORCE_OFFSCREEN"] = ori2 def test_offscreen_event_loop(): diff --git a/tests/testutils.py b/tests/testutils.py index a7f80b0..a211ddb 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -74,5 +74,5 @@ def _determine_can_use_glfw(): can_use_wgpu_lib = bool(adapter_summary) can_use_glfw = _determine_can_use_glfw() -is_ci = bool(os.getenv("CI", None)) +is_ci = bool(os.getenv("CI")) is_pypy = sys.implementation.name == "pypy"