From a1ce923413ecc7ee8e814167dbb0e0f8d2d9db7c Mon Sep 17 00:00:00 2001 From: Jan Britz Date: Thu, 30 Oct 2025 18:52:12 +0100 Subject: [PATCH 1/2] feat: work with env vars --- frontend/src/types/Manifest.generated.ts | 1 + poetry.lock | 6 +++--- pyproject.toml | 2 +- questionpy_sdk/webserver/controllers/base.py | 8 ++++++-- questionpy_sdk/webserver/server.py | 7 +++++++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/src/types/Manifest.generated.ts b/frontend/src/types/Manifest.generated.ts index 9d966a7a..36bba151 100644 --- a/frontend/src/types/Manifest.generated.ts +++ b/frontend/src/types/Manifest.generated.ts @@ -32,6 +32,7 @@ export interface Manifest { type: PackageType license: string | null permissions: PartialPackagePermissions | null + environment_variables: string[] | null tags: string[] requirements: string | string[] | null static_files: StaticFiles diff --git a/poetry.lock b/poetry.lock index 9673d52a..688e8b36 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1717,8 +1717,8 @@ watchdog = ">=6.0.0,<7.0.0" [package.source] type = "git" url = "https://github.com/questionpy-org/questionpy-server.git" -reference = "a9f052d552e68268684792e028279feabae2c2e2" -resolved_reference = "a9f052d552e68268684792e028279feabae2c2e2" +reference = "5e1681cbc19ebdbfe04a4500024f1cd6d9128ca9" +resolved_reference = "5e1681cbc19ebdbfe04a4500024f1cd6d9128ca9" [[package]] name = "requests" @@ -2028,4 +2028,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.12, <4.0" -content-hash = "95f25c50a2b7014c9239612f65b428ae732bfc6da1692cffdbfd330fe0d78825" +content-hash = "cc6ab9de7f747ff41a091c4fbc349d3c2da8e89c1c9626c8c9fe3235ea5d6b4d" diff --git a/pyproject.toml b/pyproject.toml index 286b6aa8..2ef54eba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "aiohttp >=3.11.18, <4.0.0", "pydantic >=2.11.4, <3.0.0", "PyYAML >=6.0.2, <7.0.0", - "questionpy-server @ git+https://github.com/questionpy-org/questionpy-server.git@a9f052d552e68268684792e028279feabae2c2e2", + "questionpy-server @ git+https://github.com/questionpy-org/questionpy-server.git@5e1681cbc19ebdbfe04a4500024f1cd6d9128ca9", "jinja2 >=3.1.6, <4.0.0", "aiohttp-jinja2 >=1.6, <2.0", "lxml[html-clean] >=5.4.0, <5.5.0", diff --git a/questionpy_sdk/webserver/controllers/base.py b/questionpy_sdk/webserver/controllers/base.py index 7b4417e5..eb54f090 100644 --- a/questionpy_sdk/webserver/controllers/base.py +++ b/questionpy_sdk/webserver/controllers/base.py @@ -1,7 +1,7 @@ # This file is part of the QuestionPy SDK. (https://questionpy.org) # The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md. # (c) Technische Universität Berlin, innoCampus - +import os from collections.abc import AsyncIterator from contextlib import asynccontextmanager from typing import TYPE_CHECKING @@ -26,7 +26,11 @@ def generate_api_url(self, name: str, **kwargs: str) -> URL: @asynccontextmanager async def get_worker(self) -> AsyncIterator[Worker]: async with self._webserver.worker_pool.get_worker( - self._webserver.package_location, "sdk", "sdk", self._webserver.package_permissions + self._webserver.package_location, + "sdk", + "sdk", + self._webserver.package_permissions, + os.environ.copy(), ) as worker: yield worker diff --git a/questionpy_sdk/webserver/server.py b/questionpy_sdk/webserver/server.py index bcad1383..bdb3955a 100644 --- a/questionpy_sdk/webserver/server.py +++ b/questionpy_sdk/webserver/server.py @@ -3,6 +3,7 @@ # (c) Technische Universität Berlin, innoCampus import logging +import os from pathlib import Path from types import TracebackType from typing import ClassVar, NotRequired, Self, TypedDict, Unpack @@ -59,6 +60,12 @@ async def __aenter__(self) -> Self: # Read manifest self._manifest = await read_manifest(self.package_location) + # Check if required environment variables are set + requested_environment_variables = self._manifest.environment_variables or set() + if missing_environment_variables := requested_environment_variables - os.environ.keys(): + msg = f"Required environment variables not set: {missing_environment_variables}" + raise ValueError(msg) + # Assemble worker permissions permissions = CompletePackagePermissions() if self._manifest.permissions: From 1a8396d57ea86ecbf294cf1f9488f0e424db869e Mon Sep 17 00:00:00 2001 From: Jan Britz Date: Tue, 4 Nov 2025 15:34:11 +0100 Subject: [PATCH 2/2] refactor: don't show entire stacktrace when missing env vars --- questionpy_sdk/commands/run.py | 7 ++++++- questionpy_sdk/webserver/errors.py | 5 +++++ questionpy_sdk/webserver/server.py | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/questionpy_sdk/commands/run.py b/questionpy_sdk/commands/run.py index 5b883fea..37353cf5 100644 --- a/questionpy_sdk/commands/run.py +++ b/questionpy_sdk/commands/run.py @@ -12,6 +12,7 @@ from questionpy_sdk.constants import DEFAULT_STATE_STORAGE_PATH from questionpy_sdk.watcher import Watcher from questionpy_sdk.webserver import WebServer +from questionpy_sdk.webserver.errors import EnvironmentVariablesMissingError from questionpy_sdk.webserver.server import WebServerArgs from questionpy_server.worker.impl.subprocess import SubprocessWorker from questionpy_server.worker.impl.thread import ThreadWorker @@ -94,4 +95,8 @@ def run( else: coro = async_run(webserver_args) - asyncio.run(coro) + try: + asyncio.run(coro) + except EnvironmentVariablesMissingError as e: + msg = f"The following environment variables are required to run the package: {', '.join(e.missing)}" + raise click.ClickException(msg) from e diff --git a/questionpy_sdk/webserver/errors.py b/questionpy_sdk/webserver/errors.py index 2bdb44d0..20138f72 100644 --- a/questionpy_sdk/webserver/errors.py +++ b/questionpy_sdk/webserver/errors.py @@ -36,6 +36,11 @@ class MissingAttemptDataError(MissingStateError): message = "The attempt data is missing." +class EnvironmentVariablesMissingError(Exception): + def __init__(self, missing: set[str]) -> None: + self.missing = missing + + @dataclass(config=ConfigDict(use_attribute_docstrings=True)) class DetailedServerError: """Represents a server-side error serialized for client display.""" diff --git a/questionpy_sdk/webserver/server.py b/questionpy_sdk/webserver/server.py index bdb3955a..7fc26657 100644 --- a/questionpy_sdk/webserver/server.py +++ b/questionpy_sdk/webserver/server.py @@ -24,6 +24,7 @@ from questionpy_server.worker.runtime.package_location import PackageLocation from .constants import API_PATH_PREFIX, USE_VITE_DEV_SERVER, WEBSERVER_KEY +from .errors import EnvironmentVariablesMissingError from .manifest import read_manifest log = logging.getLogger("questionpy-sdk:web-server") @@ -63,8 +64,7 @@ async def __aenter__(self) -> Self: # Check if required environment variables are set requested_environment_variables = self._manifest.environment_variables or set() if missing_environment_variables := requested_environment_variables - os.environ.keys(): - msg = f"Required environment variables not set: {missing_environment_variables}" - raise ValueError(msg) + raise EnvironmentVariablesMissingError(missing=missing_environment_variables) # Assemble worker permissions permissions = CompletePackagePermissions()