Provider HTTP transport
Superset supplies a ProviderTransport to semantic-layer providers. This keeps
HTTP policy in the host and lets an extension use the same SDK on different hosts.
Providers import only superset_core for this capability, never host HTTP helpers.
Accept the capability
The following SemanticLayer entry points require keyword-only transport=:
from_configuration(configuration, *, cache_token, context, transport, credentials=None)get_configuration_schema(configuration=None, *, transport)get_runtime_schema(configuration, runtime_data=None, *, transport)
Keep it on the constructed layer and pass it to views and discovery helpers.
It is separate from SemanticExecutionContext and does not change memo or cache
identity. Static schema discovery still receives the capability; providers must
not perform remote discovery when no validated configuration was supplied.
Set budgets for a logical operation
import time
from superset_core.semantic_layers.transport import (
ProviderResponse, ProviderTransport, RequestBudget,
)
def fetch_catalog(transport: ProviderTransport, endpoint: str) -> ProviderResponse:
budget: RequestBudget = RequestBudget(
deadline=time.monotonic() + 10.0, max_bytes=4 * 1024 * 1024,
)
return transport.request("Example", "GET", endpoint, budget=budget)
deadline is an absolute monotonic time on this process, not a wall-clock timestamp.
Reuse that deadline across pages, redirects and polling attempts. Each call's
max_bytes limits its response body. An optional consume(size) callback receives
chunk sizes before the per-call cap check, so a provider can account for a shared
operation budget. Use a provider-specific exception when that shared budget is
exhausted.
DNS, connect, TLS, headers and body reads all share the deadline. A timed-out or
truncated response is never returned as success. Resolution uses at most eight
shared workers and at most eight outstanding submissions. At capacity, new
lookups fail immediately with TIMEOUT. A caller stops waiting at its deadline,
but its submission retains capacity until the worker completes it. An OS resolver
already running cannot be cancelled; this bound does not recover stalled workers. Connected sockets are interrupted by one shared,
lazily started scheduler. Neither component starts at module import.
Responses and errors
ProviderResponse owns complete bounded body bytes, case-insensitive immutable
headers, the status, and the final URL. Its json() uses the standard library;
JSON decoding errors remain provider errors, not transport errors.
The host returns only 2xx responses. ProviderRequestError.failure identifies
INVALID_URL, REFUSED, UNREACHABLE, TIMEOUT, TOO_LARGE,
UNSUPPORTED_ENCODING, or HTTP_STATUS. For HTTP_STATUS, status contains the
status code. These are transport faults, not deliberate query refusals. Never
log URLs, credentials, query values or response payloads when handling them.
Errors and logs include the provider display name. Pass a constant label such as
MetricFlow or dbt Semantic Layer, never endpoint or configuration values.
Labels must start with an ASCII letter or digit and contain only ASCII letters,
digits, spaces, underscores or hyphens, up to 64 characters. Other labels fall
back to Provider; valid labels have trailing spaces removed. Non-string labels
also use that fallback, without converting the supplied object to a string.
The transport sends Accept-Encoding: identity and refuses other response
encodings. Non-2xx bodies are not read. Logs contain only the sanitized provider name, status and failure
category. GET may follow at most three redirects within the same scheme, hostname
and effective port. Other redirects fail with HTTP_STATUS without reading their
bodies. Redirects do not reset the deadline. No automatic retries are made.
Destination policy
URLs containing userinfo (https://user:password@host/) are refused as
INVALID_URL, both on the initial request and on redirects. Supply credentials
through the request headers instead.
By default, only HTTPS endpoints whose DNS answers are all public are allowed.
The host applies its shared is_safe_ip policy, including special IPv6 ranges.
It connects directly to a checked numeric address without resolving again, while
preserving the original hostname for TLS SNI and certificate verification.
Operators can set SEMANTIC_LAYER_ALLOW_INTERNAL_HOSTS = True in
superset_config.py to allow private addresses and HTTP for these providers.
This setting does not change OAuth or webhook policy. It never disables budgets,
TLS verification or redirect restrictions. Both settings ignore environment proxy
variables and never discover credentials from .netrc. Configure a reachable
provider endpoint rather than relying on a proxy.
In allow-internal mode, REQUESTS_CA_BUNDLE (or CURL_CA_BUNDLE as fallback)
can supply the trusted CA bundle for private TLS endpoints. Certificate and
hostname verification remain enabled. The default policy ignores both variables.
There is no per-user opt-out; trusted operators own the setting.
Use a fake in provider tests
A fake must honor the same 2xx-or-error contract. A minimal fixed-response fake:
import time
from collections.abc import Callable, Mapping
from typing import Any
from superset_core.semantic_layers.transport import (
ProviderRequestError, ProviderRequestFailure, ProviderResponse,
ProviderTransport, RequestBudget,
)
class FakeTransport(ProviderTransport):
def __init__(self) -> None:
self.calls: list[tuple[str, str]] = []
def request(
self, provider: str, method: str, url: str, *, budget: RequestBudget,
headers: Mapping[str, str] | None = None,
params: Mapping[str, Any] | None = None, json: Any = None,
consume: Callable[[int], None] | None = None,
) -> ProviderResponse:
if time.monotonic() >= budget.deadline:
raise ProviderRequestError(ProviderRequestFailure.TIMEOUT)
self.calls.append((method, url))
body: bytes = b'{"metrics": []}'
if consume is not None:
consume(len(body))
if len(body) > budget.max_bytes:
raise ProviderRequestError(ProviderRequestFailure.TOO_LARGE)
if time.monotonic() >= budget.deadline:
raise ProviderRequestError(ProviderRequestFailure.TIMEOUT)
return ProviderResponse(200, {"Content-Type": "application/json"}, body, url)
Add fixture-specific failures by raising ProviderRequestError, rather than
returning a non-2xx ProviderResponse. Keep credentials out of recorded calls.