michael-s-molina commented on code in PR #39171: URL: https://github.com/apache/superset/pull/39171#discussion_r3560958640
########## superset/extensions/storage/api.py: ########## @@ -0,0 +1,499 @@ +# 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. + +""" +REST API for extension storage. + +Provides HTTP endpoints for frontend extensions to access server-side +ephemeral storage without direct backend code. + +All operations are user-scoped by default. Use `?shared=true` query param +to access shared state visible to all users. +""" + +from __future__ import annotations + +from typing import Any + +from flask import current_app, g, request +from flask.wrappers import Response +from flask_appbuilder.api import BaseApi, expose, protect, safe + +from superset.extensions import cache_manager +from superset.extensions.storage.persistent_dao import ( + ExtensionStorageDAO, + ExtensionStorageQuotaExceeded, +) +from superset.extensions.types import LoadedExtension +from superset.extensions.utils import get_extensions +from superset.utils import json +from superset.utils.decorators import transaction + +# Key separator +SEPARATOR = ":" + +# Key prefix for extension ephemeral state +KEY_PREFIX = "superset-ext" + + +def _build_cache_key(*parts: Any) -> str: + """Build a namespaced cache key from parts.""" + return SEPARATOR.join(str(part) for part in parts) + + +def _get_extension_or_404(extension_id: str) -> LoadedExtension | None: + """Get extension by ID or return None if not found.""" + extensions = get_extensions() + return extensions.get(extension_id) + + +def _parse_ttl(body: dict[str, Any]) -> tuple[int | None, str | None]: + """Parse and validate TTL from request body. + + Returns: + (ttl, error_message) - error_message is set if the value is missing or invalid. + """ + if "ttl" not in body: + return None, "Field 'ttl' is required" + try: + ttl = int(body["ttl"]) + except (TypeError, ValueError): + return None, "Field 'ttl' must be a positive integer" + if ttl <= 0: + return None, "Field 'ttl' must be a positive integer" + max_ttl = current_app.config.get("EXTENSIONS_EPHEMERAL_STORAGE", {}).get("MAX_TTL") + if max_ttl is not None and ttl > max_ttl: + return None, f"Field 'ttl' must not exceed {max_ttl} seconds" + return ttl, None + + +def _build_storage_key(extension_id: str, key: str, shared: bool) -> str: + """Build the cache key based on scope (user or shared).""" + if shared: + return _build_cache_key(KEY_PREFIX, extension_id, "shared", key) + user_id = g.user.id + return _build_cache_key(KEY_PREFIX, extension_id, "user", user_id, key) Review Comment: Done -- 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]
