codeant-ai-for-open-source[bot] commented on code in PR #39171: URL: https://github.com/apache/superset/pull/39171#discussion_r3624434959
########## superset-core/src/superset_core/extensions/storage/ephemeral.py: ########## @@ -0,0 +1,144 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Ephemeral State API for superset-core extensions (Tier 2 Storage). + +Provides short-lived KV storage backed by the configured server-side cache +backend (Redis, Memcached, or filesystem). Automatically expires based on TTL. +Not guaranteed to survive server restarts. + +Host implementations will replace the EphemeralState class during initialization +with a concrete implementation providing actual functionality. + +Cache keys are namespaced automatically: +- User-scoped (default): superset-ext:{extension_id}:user:{user_id}:{key} +- Shared (global): superset-ext:{extension_id}:shared:{key} + +Usage (via extension context - preferred): + from superset_core.extensions.context import get_context + + ctx = get_context() + + # User-scoped state (default - private to current user) + ctx.storage.ephemeral.get('preference') + ctx.storage.ephemeral.set('preference', 'compact', EphemeralSetOptions(ttl=3600)) + ctx.storage.ephemeral.remove('preference') + + # Shared state (explicit opt-in - visible to all users) + ctx.storage.ephemeral.shared.get('job_progress') + ctx.storage.ephemeral.shared.set( + 'job_progress', {'pct': 42}, EphemeralSetOptions(ttl=3600) + ) + ctx.storage.ephemeral.shared.remove('job_progress') +""" + +from dataclasses import dataclass +from typing import Any, ClassVar, Protocol + + +@dataclass(frozen=True) +class EphemeralSetOptions: + """ + Options for an ephemeral state `set` call. + + `ttl` is required (no default): every write must have an explicit + expiration, matching the frontend `EphemeralSetOptions` and the REST API. + """ + + ttl: int + #: Name of the codec used to encode `value`, e.g. "json" (default). + codec: str = "json" + + +class EphemeralStateAccessor(Protocol): + """Protocol for scoped ephemeral state access.""" + + def get(self, key: str) -> Any: + """Get a value from ephemeral state.""" + ... + + def set(self, key: str, value: Any, options: EphemeralSetOptions) -> None: + """Set a value in ephemeral state with TTL.""" + ... + + def remove(self, key: str) -> None: + """Remove a value from ephemeral state.""" + ... + + +class EphemeralState: + """ + Tier 2 ephemeral state storage for extensions. + + Backed by the configured server-side cache (Redis, Memcached, or filesystem). + Data expires based on TTL and is not guaranteed to survive server restarts. + + Host implementations will replace this class during initialization + with a concrete implementation providing actual functionality. + + All operations are user-scoped by default (private to the current user). + Use `.shared` to access state that is visible to all users. + """ + + @staticmethod + def get(key: str) -> Any: + """ + Get a value from user-scoped ephemeral state. + + Data is automatically scoped to the current authenticated user. + Other users cannot see or modify this data. + + :param key: The key to retrieve. + :returns: The stored value, or None if not found or expired. + """ + raise NotImplementedError("Class will be replaced during initialization") + + @staticmethod + def set(key: str, value: Any, options: EphemeralSetOptions) -> None: + """ + Set a value in user-scoped ephemeral state with TTL. + + Data is automatically scoped to the current authenticated user. + Other users cannot see or modify this data. + + :param key: The key to store. + :param value: The value to store, encoded with `options.codec` + (default "json"). The encoded value must not exceed + MAX_VALUE_SIZE from config. + :param options: `EphemeralSetOptions`, e.g. `ttl=3600`. Required — + `ttl` must not exceed MAX_TTL from config. `codec="pickle"` + stores a value that isn't JSON-serializable. + """ + raise NotImplementedError("Class will be replaced during initialization") + + @staticmethod + def remove(key: str) -> None: + """ + Remove a value from user-scoped ephemeral state. + + Data is automatically scoped to the current authenticated user. + + :param key: The key to remove. + """ + raise NotImplementedError("Class will be replaced during initialization") + + #: Shared (global) ephemeral state accessor. + #: Data stored via this accessor is visible to all users of the extension. + #: WARNING: Do not store user-specific or sensitive data here. + #: Host implementations will set this during initialization. + shared: ClassVar[EphemeralStateAccessor] Review Comment: **Suggestion:** `shared` is only annotated but never initialized, so accessing `EphemeralState.shared` before host-side class replacement will raise `AttributeError` instead of the expected placeholder behavior. Initialize it with a stub accessor (that raises `NotImplementedError`) or set a safe default during class definition to keep the API contract consistent. [possible bug] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ Direct use of shared ephemeral storage crashes with AttributeError. - ⚠️ Misconfigured hosts crash when shared accessor is used. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. Start a Python shell with the PR code installed and import the class: `from superset_core.extensions.storage.ephemeral import EphemeralState` (class defined at line 84 in this file). 2. Without any host-side initialization or class replacement, access the shared accessor: `EphemeralState.shared.get("foo")`, referencing the annotated attribute at line 144. 3. Python attempts to look up the `shared` attribute on `EphemeralState` but finds only a type annotation; no actual class attribute was created during definition. 4. Observe `AttributeError: type object 'EphemeralState' has no attribute 'shared'` instead of the placeholder `NotImplementedError` used by the static methods at lines 98, 111, and 129, leading to an inconsistent failure mode for shared ephemeral storage before host injection. ``` </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=33228fcdc43646cabb7479710f10b0c2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=33228fcdc43646cabb7479710f10b0c2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** superset-core/src/superset_core/extensions/storage/ephemeral.py **Line:** 144:144 **Comment:** *Possible Bug: `shared` is only annotated but never initialized, so accessing `EphemeralState.shared` before host-side class replacement will raise `AttributeError` instead of the expected placeholder behavior. Initialize it with a stub accessor (that raises `NotImplementedError`) or set a safe default during class definition to keep the API contract consistent. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39171&comment_hash=a43b2ed195e85a6d7da0cba41e0d405e03a2754d58a3faf83b228b9949c326e5&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39171&comment_hash=a43b2ed195e85a6d7da0cba41e0d405e03a2754d58a3faf83b228b9949c326e5&reaction=dislike'>👎</a> -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
