From 567ce586bfaaa82d0bad413c839b3eb6f867771c Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:04:51 +0200 Subject: [PATCH 1/2] add superset_openlineage module to superset image --- CHANGELOG.md | 5 + superset/Dockerfile | 8 + .../stackable/superset-openlineage/README.md | 76 ++++++++ .../superset-openlineage/pyproject.toml | 21 ++ .../superset_openlineage/__init__.py | 36 ++++ .../superset_openlineage/client.py | 61 ++++++ .../superset_openlineage/context.py | 69 +++++++ .../superset_openlineage/event_logger.py | 122 ++++++++++++ .../superset_openlineage/facets.py | 27 +++ .../superset_openlineage/jobname.py | 52 +++++ .../superset_openlineage/lifecycle.py | 31 +++ .../superset_openlineage/lineage.py | 184 ++++++++++++++++++ .../superset_openlineage/naming.py | 78 ++++++++ .../superset_openlineage/query_logger.py | 96 +++++++++ .../superset_openlineage/settings.py | 41 ++++ .../superset_openlineage/tests/__init__.py | 16 ++ .../superset_openlineage/tests/test_client.py | 45 +++++ .../tests/test_context.py | 61 ++++++ .../tests/test_end_to_end.py | 61 ++++++ .../tests/test_event_logger.py | 94 +++++++++ .../tests/test_jobname.py | 79 ++++++++ .../tests/test_lifecycle.py | 70 +++++++ .../tests/test_lineage_columns.py | 92 +++++++++ .../tests/test_lineage_outputs.py | 80 ++++++++ .../tests/test_lineage_tables.py | 47 +++++ .../superset_openlineage/tests/test_naming.py | 58 ++++++ .../tests/test_openlineage_api.py | 41 ++++ .../tests/test_query_logger.py | 98 ++++++++++ 28 files changed, 1749 insertions(+) create mode 100644 superset/stackable/superset-openlineage/README.md create mode 100644 superset/stackable/superset-openlineage/pyproject.toml create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/__init__.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/client.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/context.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/event_logger.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/facets.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/jobname.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/lineage.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/naming.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/query_logger.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/settings.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py create mode 100644 superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 961f1b29c..6cffb6947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- superset: Add the `superset-openlineage` integration that emits OpenLineage events for SQL Lab queries and chart/dashboard data fetches; config-only, inert unless enabled in `superset_config.py` ([#XXXX]). + ### Removed - omid: remove 1.1.2 ([#1593]). [#1593]: https://github.com/stackabletech/docker-images/pull/1593 +[#XXXX]: https://github.com/stackabletech/docker-images/pull/XXXX ## [26.7.0] - 2026-07-21 diff --git a/superset/Dockerfile b/superset/Dockerfile index 99c72e425..6789f20ab 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -77,6 +77,7 @@ RUN microdnf update \ COPY superset/stackable/constraints/${PRODUCT_VERSION}/constraints.txt /tmp/constraints.txt COPY superset/stackable/constraints/${PRODUCT_VERSION}/build-constraints.txt /tmp/build-constraints.txt COPY --from=opa-authorizer-builder /tmp/opa-authorizer/dist/opa_authorizer-0.1.0-py3-none-any.whl /tmp/ +COPY superset/stackable/superset-openlineage /tmp/superset-openlineage COPY --chown=${STACKABLE_USER_UID}:0 superset/stackable/patches/patchable.toml /stackable/src/superset/stackable/patches/patchable.toml COPY --chown=${STACKABLE_USER_UID}:0 superset/stackable/patches/${PRODUCT_VERSION} /stackable/src/superset/stackable/patches/${PRODUCT_VERSION} @@ -169,6 +170,13 @@ fi uv pip install --no-cache-dir /tmp/opa_authorizer-0.1.0-py3-none-any.whl +# Install the Stackable OpenLineage integration for Superset. It emits OpenLineage +# events for SQL Lab queries and chart/dashboard data fetches. It is config-only and +# stays inert unless enabled in superset_config.py (QUERY_LOGGER, EVENT_LOGGER and +# init_app via FLASK_APP_MUTATOR); see superset/stackable/superset-openlineage/README.md. +# sqlglot and SQLAlchemy are provided by Superset itself; only openlineage-python is pulled in. +uv pip install --no-cache-dir /tmp/superset-openlineage + # Setuptools 82+ removed pkg_resources, which is still needed by Superset 4.x # dependencies. Re-pin after all other installs in case newer versions # have been pulled in by other dependencies. diff --git a/superset/stackable/superset-openlineage/README.md b/superset/stackable/superset-openlineage/README.md new file mode 100644 index 000000000..341efa81c --- /dev/null +++ b/superset/stackable/superset-openlineage/README.md @@ -0,0 +1,76 @@ +# superset-openlineage + +OpenLineage integration for Apache Superset, shipped in the Stackable Superset +image. It emits OpenLineage events when SQL Lab queries run and when dashboard +charts fetch data. + +It is **config-only**: no changes are made to the Superset source tree, and the +plugin stays completely inert unless it is explicitly enabled in +`superset_config.py`. The package is installed into the image (see the +`superset/Dockerfile` build stage), but activation is left to the operator/admin. + +## Enable it + +Add the following to `superset_config.py`: + +```python +from superset_openlineage import ( + build_query_logger, + OpenLineageEventLogger, + init_app, +) + +QUERY_LOGGER = build_query_logger() +EVENT_LOGGER = OpenLineageEventLogger() + +# Registers a teardown_request that clears the request-scoped stash +# (required to avoid cross-request lineage mis-attribution on reused workers). +def FLASK_APP_MUTATOR(app): + init_app(app) +``` + +## Configure the transport + +Point the client at a lineage backend with standard OpenLineage configuration +(environment variables or `openlineage.yml`): + +```bash +export OPENLINEAGE_URL=http://marquez:5000 +export OPENLINEAGE_API_KEY= # optional +# or set OPENLINEAGE_DISABLED=true to turn emission off entirely +``` + +## Plugin settings + +| Env var | Default | Meaning | +|---|---|---| +| `SUPERSET_OPENLINEAGE_ENABLED` | `true` | Master switch for the plugin | +| `SUPERSET_OPENLINEAGE_NAMESPACE` | `superset` | OpenLineage job namespace | +| `SUPERSET_OPENLINEAGE_PRODUCER` | Superset repo URL | `producer` URI on events | +| `SUPERSET_OPENLINEAGE_JOB_NAME` | `$source.$identity` | Job-name template (`string.Template`) | + +### Job-name template + +A Python `string.Template`. Placeholders: `$source`, `$identity`, +`$sql_editor_id`, `$client_id`, `$slice_id`, `$dashboard_id`, `$sql_hash`, +`$schema`, `$catalog`, `$database`, `$username`. Placeholders that do not apply +in a given context render empty and surrounding separators collapse; an empty +result falls back to `$source.$sql_hash`. + +## Known limitations + +- Failed queries and cache hits emit no events (the config-only hooks provide no + failure or cache signal). +- Column-level lineage is emitted only for `CTAS`/`CVAS` statements (which have a + real output table); plain `SELECT` queries (charts/dashboards and most SQL Lab) + carry table-level inputs only. +- Queries executed via Superset's newer SQL execution engine + (`superset/sql/execution/*`) are not covered yet — they invoke `QUERY_LOGGER` + but emit no completion event, so they produce no lineage (their pending stash + entries are reclaimed by the teardown hook). + +## Tests + +Unit tests live under `superset_openlineage/tests/` and require +`openlineage-python`, `sqlglot`, `SQLAlchemy` and `pytest`. They are not +installed into the image. diff --git a/superset/stackable/superset-openlineage/pyproject.toml b/superset/stackable/superset-openlineage/pyproject.toml new file mode 100644 index 000000000..a1575b080 --- /dev/null +++ b/superset/stackable/superset-openlineage/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools>=64"] +build-backend = "setuptools.build_meta" + +[project] +name = "superset-openlineage" +version = "0.1.0" +description = "OpenLineage integration for Apache Superset" +# Keep in sync with the python-version build arguments in superset/boil-config.toml. +requires-python = ">=3.10" +# Only openlineage-python is pulled in here. sqlglot and SQLAlchemy are runtime +# dependencies of Apache Superset itself, so they are intentionally NOT declared +# to avoid conflicting with Superset's own pins. +dependencies = [ + "openlineage-python>=1.30,<2", +] + +[tool.setuptools] +# Only the package is installed; the tests/ subpackage is intentionally excluded +# from the wheel. +packages = ["superset_openlineage"] diff --git a/superset/stackable/superset-openlineage/superset_openlineage/__init__.py b/superset/stackable/superset-openlineage/superset_openlineage/__init__.py new file mode 100644 index 000000000..1e58bc0dd --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/__init__.py @@ -0,0 +1,36 @@ +# 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. +"""OpenLineage integration for Apache Superset (config-only plugin).""" + +__all__ = ["build_query_logger", "OpenLineageEventLogger", "init_app"] + + +# lazy re-export to avoid import cost at config load +def __getattr__(name: str) -> object: + if name == "build_query_logger": + from superset_openlineage.query_logger import build_query_logger + + return build_query_logger + if name == "OpenLineageEventLogger": + from superset_openlineage.event_logger import OpenLineageEventLogger + + return OpenLineageEventLogger + if name == "init_app": + from superset_openlineage.lifecycle import init_app + + return init_app + raise AttributeError(name) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/client.py b/superset/stackable/superset-openlineage/superset_openlineage/client.py new file mode 100644 index 000000000..052f8911d --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/client.py @@ -0,0 +1,61 @@ +# 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. +from __future__ import annotations + +import logging +from typing import Any + +from superset_openlineage import settings + +logger = logging.getLogger(__name__) + +_client: Any = None +_initialized: bool = False + + +def reset_client() -> None: + global _client, _initialized + _client = None + _initialized = False + + +def get_client() -> Any: + global _client, _initialized + if _initialized: + return _client + _initialized = True + if not settings.is_enabled(): + _client = None + return None + try: + from openlineage.client import OpenLineageClient + + _client = OpenLineageClient() + except Exception as ex: # noqa: BLE001 - never break Superset on OL init + logger.warning("OpenLineage: client init failed, disabling: %s", ex) + _client = None + return _client + + +def emit(event: Any) -> None: + client = get_client() + if client is None: + return + try: + client.emit(event) + except Exception as ex: # noqa: BLE001 - emission must never break a query + logger.warning("OpenLineage: emit failed, dropping event: %s", ex) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/context.py b/superset/stackable/superset-openlineage/superset_openlineage/context.py new file mode 100644 index 000000000..1cd8e1b58 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/context.py @@ -0,0 +1,69 @@ +# 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. +from __future__ import annotations + +from contextvars import ContextVar +from dataclasses import dataclass, field +from typing import Any + +_PENDING: ContextVar[list["PendingRun"] | None] = ContextVar("ol_pending", default=None) + + +@dataclass +class PendingRun: + run_id: str + sql: str + dialect: str | None + source: str + sql_hash: str + schema: str | None + catalog: str | None + database: str | None + username: str | None + sql_editor_id: str | None + client_id: str | None + start_time: float + inputs: list[Any] = field(default_factory=list) + outputs: list[Any] = field(default_factory=list) + column_lineage: Any = None + + +def _stack() -> list[PendingRun]: + stack = _PENDING.get() + if stack is None: + stack = [] + _PENDING.set(stack) + return stack + + +def push_pending(run: PendingRun) -> None: + stack = list(_stack()) + stack.append(run) + _PENDING.set(stack) + + +def pop_pending() -> PendingRun | None: + stack = list(_stack()) + if not stack: + return None + run = stack.pop() + _PENDING.set(stack) + return run + + +def clear_pending() -> None: + _PENDING.set([]) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py new file mode 100644 index 000000000..3c8f40f54 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py @@ -0,0 +1,122 @@ +# 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. +from __future__ import annotations + +import logging +from datetime import datetime, timezone +from typing import Any + +from openlineage.client.event_v2 import Job, Run, RunEvent, RunState +from openlineage.client.facet_v2 import job_type_job, sql_job + +from superset.utils.log import DBEventLogger +from superset_openlineage import settings +from superset_openlineage.client import emit +from superset_openlineage.context import PendingRun, pop_pending +from superset_openlineage.facets import SupersetJobFacet +from superset_openlineage.jobname import render_job_name, resolve_identity + +logger = logging.getLogger(__name__) + + +class OpenLineageEventLogger(DBEventLogger): + """DBEventLogger that also emits OpenLineage run events on query success.""" + + def log( # type: ignore[override] + self, user_id: int | None, action: str, *args: Any, **kwargs: Any + ) -> None: + # Always preserve default DB audit logging. + self._persist(user_id, action, *args, **kwargs) + if action != "execute_sql": + return + try: + self._emit_lineage(kwargs) + except Exception as ex: # noqa: BLE001 - emission must never break a query + logger.warning("OpenLineage: event emission failed: %s", ex) + + def _persist( + self, user_id: int | None, action: str, *args: Any, **kwargs: Any + ) -> None: + super().log(user_id, action, *args, **kwargs) + + def _emit_lineage(self, kwargs: dict[str, Any]) -> None: + pending = pop_pending() + if pending is None: + return + job = self._build_job(pending, kwargs) + run = Run(runId=pending.run_id) + producer = settings.get_producer() + start_iso = ( + datetime.fromtimestamp(pending.start_time, tz=timezone.utc).isoformat() + if pending.start_time + else datetime.now(timezone.utc).isoformat() + ) + end_iso = datetime.now(timezone.utc).isoformat() + emit( + RunEvent( + eventType=RunState.START, + eventTime=start_iso, + run=run, + job=job, + inputs=pending.inputs, + outputs=pending.outputs, + producer=producer, + ) + ) + emit( + RunEvent( + eventType=RunState.COMPLETE, + eventTime=end_iso, + run=run, + job=job, + inputs=pending.inputs, + outputs=pending.outputs, + producer=producer, + ) + ) + + def _build_job(self, pending: PendingRun, kwargs: dict[str, Any]) -> Job: + values = { + "source": pending.source, + "sql_editor_id": pending.sql_editor_id, + "client_id": pending.client_id, + "slice_id": kwargs.get("slice_id"), + "dashboard_id": kwargs.get("dashboard_id"), + "sql_hash": pending.sql_hash, + "schema": pending.schema, + "catalog": pending.catalog, + "database": pending.database, + "username": pending.username, + } + values["identity"] = resolve_identity(values) + name = render_job_name(settings.get_job_name_template(), values) + job_facets: dict[str, Any] = { + "jobType": job_type_job.JobTypeJobFacet( + processingType="BATCH", + integration="SUPERSET", + jobType="QUERY", + ), + "sql": sql_job.SQLJobFacet( + query=pending.sql, dialect=pending.dialect or "" + ), + "superset": SupersetJobFacet( + source=pending.source, + dashboardId=kwargs.get("dashboard_id"), + username=pending.username, + ), + } + return Job(namespace=settings.get_namespace(), name=name, facets=job_facets) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/facets.py b/superset/stackable/superset-openlineage/superset_openlineage/facets.py new file mode 100644 index 000000000..a4494c621 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/facets.py @@ -0,0 +1,27 @@ +# 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. +from __future__ import annotations + +import attr +from openlineage.client.facet_v2 import JobFacet + + +@attr.define +class SupersetJobFacet(JobFacet): + source: str | None = None + dashboardId: int | None = None # noqa: N815 - OpenLineage uses camelCase + username: str | None = None diff --git a/superset/stackable/superset-openlineage/superset_openlineage/jobname.py b/superset/stackable/superset-openlineage/superset_openlineage/jobname.py new file mode 100644 index 000000000..a331a8412 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/jobname.py @@ -0,0 +1,52 @@ +# 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. +from __future__ import annotations + +import re +from string import Template + + +def resolve_identity(values: dict[str, object]) -> str: + source = values.get("source") + if source == "chart": + candidates = [values.get("slice_id"), values.get("sql_hash")] + else: # sqllab (and any other source) + candidates = [ + values.get("sql_editor_id"), + values.get("client_id"), + values.get("sql_hash"), + ] + for candidate in candidates: + if candidate: + return str(candidate) + return "" + + +def _normalize(name: str) -> str: + # collapse repeated dots and strip leading/trailing separators + name = re.sub(r"\.{2,}", ".", name) + return name.strip(". ") + + +def render_job_name(template: str, values: dict[str, object]) -> str: + safe = {k: ("" if v is None else str(v)) for k, v in values.items()} + rendered = Template(template).safe_substitute(safe) + normalized = _normalize(rendered) + if not normalized: + fallback = f"{safe.get('source', 'superset')}.{safe.get('sql_hash', '')}" + return _normalize(fallback) + return normalized diff --git a/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py b/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py new file mode 100644 index 000000000..37bc19921 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py @@ -0,0 +1,31 @@ +# 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. +from __future__ import annotations + +from typing import Any + +from superset_openlineage.context import clear_pending + + +def init_app(app: Any) -> None: + """Register a teardown hook that clears the request-scoped OpenLineage + stash at the end of each app context, preventing cross-request leaks/ + mis-attribution on reused worker threads.""" + + @app.teardown_appcontext + def _clear_openlineage_stash(exc: BaseException | None = None) -> None: + clear_pending() diff --git a/superset/stackable/superset-openlineage/superset_openlineage/lineage.py b/superset/stackable/superset-openlineage/superset_openlineage/lineage.py new file mode 100644 index 000000000..341032c8b --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/lineage.py @@ -0,0 +1,184 @@ +# 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. +from __future__ import annotations + +import logging +from typing import Any + +import sqlglot +from openlineage.client.event_v2 import InputDataset, OutputDataset +from openlineage.client.facet_v2 import column_lineage_dataset, schema_dataset +from sqlalchemy.engine import URL +from sqlglot import exp +from sqlglot.lineage import lineage as sqlglot_lineage + +from superset_openlineage.naming import dataset_name, dataset_namespace, TableRef + +logger = logging.getLogger(__name__) + + +def _parse(sql: str, dialect: str | None) -> exp.Expression | None: + try: + return sqlglot.parse_one(sql, read=dialect) + except Exception as ex: # noqa: BLE001 - best-effort parse + logger.debug("OpenLineage: could not parse SQL: %s", ex) + return None + + +def extract_tables(sql: str, dialect: str | None) -> list[TableRef]: + expression = _parse(sql, dialect) + if expression is None: + return [] + cte_names = {cte.alias_or_name for cte in expression.find_all(exp.CTE)} + seen: set[TableRef] = set() + refs: list[TableRef] = [] + for tbl in expression.find_all(exp.Table): + if tbl.name in cte_names: + continue + ref = TableRef( + catalog=tbl.catalog or None, + schema=tbl.db or None, + table=tbl.name, + ) + if ref not in seen: + seen.add(ref) + refs.append(ref) + return refs + + +def build_input_datasets(sql: str, url: URL, dialect: str | None) -> list[InputDataset]: + namespace = dataset_namespace(url) + datasets: list[InputDataset] = [] + for ref in extract_tables(sql, dialect): + datasets.append(InputDataset(namespace=namespace, name=dataset_name(url, ref))) + return datasets + + +def _output_columns(expression: exp.Expression) -> list[str]: + select = expression.find(exp.Select) + if select is None: + return [] + names = [] + for projection in select.expressions: + name = projection.alias_or_name + if name and name != "*": + names.append(name) + return names + + +def _table_ref_for(node: Any) -> TableRef | None: + tbl = node.expression.find(exp.Table) if node.expression else None + if tbl is None: + return None + return TableRef(catalog=tbl.catalog or None, schema=tbl.db or None, table=tbl.name) + + +def build_column_lineage_facet( + sql: str, url: URL, dialect: str | None +) -> "column_lineage_dataset.ColumnLineageDatasetFacet | None": + expression = _parse(sql, dialect) + if expression is None: + return None + try: + namespace = dataset_namespace(url) + fields: dict[str, column_lineage_dataset.Fields] = {} + for out_col in _output_columns(expression): + input_fields: list[column_lineage_dataset.InputField] = [] + root = sqlglot_lineage(out_col, sql, dialect=dialect) + for leaf in root.walk(): + # only true leaves (no further upstream) reference a + # source column of a real table; intermediate/CTE nodes + # must not be reported as sources (I5). + if leaf is root or leaf.downstream or not leaf.name: + continue + ref = _table_ref_for(leaf) + if ref is None or "." not in leaf.name: + continue + col = leaf.name.split(".")[-1] + input_fields.append( + column_lineage_dataset.InputField( + namespace=namespace, + name=dataset_name(url, ref), + field=col, + ) + ) + if input_fields: + fields[out_col] = column_lineage_dataset.Fields( + inputFields=input_fields + ) + return ( + column_lineage_dataset.ColumnLineageDatasetFacet(fields=fields) + if fields + else None + ) + except Exception as ex: # noqa: BLE001 - column lineage is best-effort + logger.debug("OpenLineage: column lineage failed: %s", ex) + return None + + +def _ctas_target(expression: exp.Expression) -> tuple[TableRef, exp.Expression] | None: + """Return (target table ref, inner SELECT) for a CTAS/CVAS Create, else None.""" + if not isinstance(expression, exp.Create): + return None + query = expression.args.get("expression") + if query is None or not isinstance(query, exp.Query): + return None + tbl = expression.find(exp.Table) + if tbl is None: + return None + ref = TableRef(catalog=tbl.catalog or None, schema=tbl.db or None, table=tbl.name) + return ref, query + + +def build_output_datasets( + sql: str, url: URL, dialect: str | None +) -> list[OutputDataset]: + """Build the OUTPUT dataset for CTAS/CVAS statements only. + + Column lineage is only meaningful when there is a real output table, + so plain SELECT queries (and any other non-CTAS/CVAS statement) + intentionally return an empty list. + """ + try: + expression = _parse(sql, dialect) + if expression is None: + return [] + target = _ctas_target(expression) + if target is None: + return [] + ref, inner_query = target + namespace = dataset_namespace(url) + name = dataset_name(url, ref) + + facets: dict[str, Any] = {} + columns = _output_columns(inner_query) + if columns: + facets["schema"] = schema_dataset.SchemaDatasetFacet( + fields=[ + schema_dataset.SchemaDatasetFacetFields(name=col) for col in columns + ] + ) + column_lineage = build_column_lineage_facet( + inner_query.sql(dialect=dialect), url, dialect + ) + if column_lineage is not None: + facets["columnLineage"] = column_lineage + + return [OutputDataset(namespace=namespace, name=name, facets=facets)] + except Exception as ex: # noqa: BLE001 - output lineage is best-effort + logger.debug("OpenLineage: output dataset extraction failed: %s", ex) + return [] diff --git a/superset/stackable/superset-openlineage/superset_openlineage/naming.py b/superset/stackable/superset-openlineage/superset_openlineage/naming.py new file mode 100644 index 000000000..99b78398d --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/naming.py @@ -0,0 +1,78 @@ +# 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. +from __future__ import annotations + +from dataclasses import dataclass + +from sqlalchemy.engine import URL + +# SQLAlchemy backend name -> OpenLineage namespace scheme. +_SCHEME = { + "postgresql": "postgres", + "postgres": "postgres", + "mysql": "mysql", + "redshift": "redshift", + "snowflake": "snowflake", + "bigquery": "bigquery", + "trino": "trino", + "presto": "presto", +} + +# SQLAlchemy backend name -> sqlglot dialect. +_DIALECT = { + "postgresql": "postgres", + "postgres": "postgres", + "mysql": "mysql", + "redshift": "redshift", + "snowflake": "snowflake", + "bigquery": "bigquery", + "trino": "trino", + "presto": "presto", + "sqlite": "sqlite", + "mssql": "tsql", + "oracle": "oracle", +} + + +@dataclass(frozen=True) +class TableRef: + catalog: str | None + schema: str | None + table: str + + +def _backend(url: URL) -> str: + # e.g. "postgresql+psycopg2" -> "postgresql" + return url.get_backend_name() + + +def dataset_namespace(url: URL) -> str: + scheme = _SCHEME.get(_backend(url), _backend(url)) + host = url.host or "localhost" + return f"{scheme}://{host}:{url.port}" if url.port else f"{scheme}://{host}" + + +def dataset_name(url: URL, ref: TableRef) -> str: + parts = [ref.catalog, ref.schema, ref.table] + if url.database and ref.catalog is None: + # Prefix the connection database when the table has no explicit catalog. + parts = [url.database, ref.schema, ref.table] + return ".".join(p for p in parts if p) + + +def sqlglot_dialect(url: URL) -> str | None: + return _DIALECT.get(_backend(url)) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py new file mode 100644 index 000000000..ea17bc3de --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py @@ -0,0 +1,96 @@ +# 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. +from __future__ import annotations + +import hashlib +import logging +import time +from typing import Any, Callable + +from sqlalchemy.engine import make_url + +from superset_openlineage.context import PendingRun, push_pending +from superset_openlineage.lineage import build_input_datasets, build_output_datasets +from superset_openlineage.naming import sqlglot_dialect + +logger = logging.getLogger(__name__) + + +def _sql_hash(sql: str) -> str: + return hashlib.sha1(sql.encode("utf-8", "ignore")).hexdigest()[:12] # noqa: S324 + + +def _source_for(client: str | None) -> str: + return "sqllab" if client == "superset.sql_lab" else "chart" + + +def build_query_logger() -> Callable[..., None]: + def query_logger( + database: Any, + query: str, + schema: str | None = None, + client: str | None = None, + security_manager: Any = None, + log_params: dict[str, Any] | None = None, + **_: Any, + ) -> None: + try: + from openlineage.client.uuid import generate_new_uuid + + url = make_url(str(database)) + dialect = sqlglot_dialect(url) + inputs = build_input_datasets(query, url, dialect) + outputs = build_output_datasets(query, url, dialect) + username = None + try: + username = getattr( + getattr(security_manager, "current_user", None), + "username", + None, + ) + except Exception: # noqa: BLE001 + username = None + log_params = log_params or {} + catalog_value = url.query.get("catalog") if url.query else None + catalog: str | None + if isinstance(catalog_value, (list, tuple)): + catalog = catalog_value[0] if catalog_value else None + else: + catalog = catalog_value + push_pending( + PendingRun( + run_id=str(generate_new_uuid()), + sql=query, + dialect=dialect, + source=_source_for(client), + sql_hash=_sql_hash(query), + schema=schema, + catalog=catalog, + database=url.database, + username=username, + sql_editor_id=log_params.get("sql_editor_id"), + client_id=log_params.get("client_id"), + start_time=time.time(), + inputs=inputs, + outputs=outputs, + column_lineage=None, + ) + ) + except Exception as ex: # noqa: BLE001 - never break the query + logger.warning("OpenLineage: query_logger failed: %s", ex) + + return query_logger diff --git a/superset/stackable/superset-openlineage/superset_openlineage/settings.py b/superset/stackable/superset-openlineage/superset_openlineage/settings.py new file mode 100644 index 000000000..1d5f96d9d --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/settings.py @@ -0,0 +1,41 @@ +# 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. +from __future__ import annotations + +import os + +_PRODUCER_DEFAULT = "https://github.com/apache/superset" + + +def get_namespace() -> str: + return os.environ.get("SUPERSET_OPENLINEAGE_NAMESPACE", "superset") + + +def get_producer() -> str: + return os.environ.get("SUPERSET_OPENLINEAGE_PRODUCER", _PRODUCER_DEFAULT) + + +def get_job_name_template() -> str: + return os.environ.get("SUPERSET_OPENLINEAGE_JOB_NAME", "$source.$identity") + + +def is_enabled() -> bool: + return os.environ.get("SUPERSET_OPENLINEAGE_ENABLED", "true").lower() not in ( + "false", + "0", + "no", + ) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py new file mode 100644 index 000000000..13a83393a --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py new file mode 100644 index 000000000..a87df5709 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py @@ -0,0 +1,45 @@ +# 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. +from typing import Any + +import superset_openlineage.client as client_mod +from superset_openlineage.client import emit, get_client, reset_client + + +def test_disabled_returns_no_client(monkeypatch: Any) -> None: + reset_client() + monkeypatch.setenv("SUPERSET_OPENLINEAGE_ENABLED", "false") + assert get_client() is None + + +def test_emit_never_raises_when_disabled(monkeypatch: Any) -> None: + reset_client() + monkeypatch.setenv("SUPERSET_OPENLINEAGE_ENABLED", "false") + emit(object()) # must not raise + + +def test_emit_swallows_client_errors(monkeypatch: Any) -> None: + reset_client() + monkeypatch.setenv("SUPERSET_OPENLINEAGE_ENABLED", "true") + + class Boom: + def emit(self, _event: Any) -> None: + raise RuntimeError("transport down") + + monkeypatch.setattr(client_mod, "_client", Boom(), raising=False) + monkeypatch.setattr(client_mod, "_initialized", True, raising=False) + emit(object()) # must not raise diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py new file mode 100644 index 000000000..6e2238caa --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py @@ -0,0 +1,61 @@ +# 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. +from superset_openlineage.context import ( + clear_pending, + PendingRun, + pop_pending, + push_pending, +) + + +def _run(run_id: str) -> PendingRun: + return PendingRun( + run_id=run_id, + sql="SELECT 1", + dialect="postgres", + source="sqllab", + sql_hash="abc", + schema=None, + catalog=None, + database="d", + username="u", + sql_editor_id=None, + client_id=None, + start_time=0.0, + inputs=[], + outputs=[], + column_lineage=None, + ) + + +def test_push_then_pop_is_lifo() -> None: + clear_pending() + push_pending(_run("a")) + push_pending(_run("b")) + top = pop_pending() + assert top is not None + assert top.run_id == "b" + next_ = pop_pending() + assert next_ is not None + assert next_.run_id == "a" + assert pop_pending() is None + + +def test_clear_empties_stack() -> None: + push_pending(_run("a")) + clear_pending() + assert pop_pending() is None diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py new file mode 100644 index 000000000..fae7ae5f8 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py @@ -0,0 +1,61 @@ +# 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. +from typing import Any + +import superset_openlineage.client as client_mod +from superset_openlineage.client import reset_client +from superset_openlineage.context import clear_pending +from superset_openlineage.event_logger import OpenLineageEventLogger +from superset_openlineage.query_logger import build_query_logger + + +def test_sqllab_query_emits_events_via_file_transport( + tmp_path: Any, monkeypatch: Any +) -> None: + reset_client() + clear_pending() + # capture emitted events in-memory instead of a real transport + events = [] + monkeypatch.setattr(client_mod, "_initialized", True, raising=False) + monkeypatch.setattr( + client_mod, + "_client", + type("C", (), {"emit": lambda self, e: events.append(e)})(), + raising=False, + ) + monkeypatch.setattr( + OpenLineageEventLogger, "_persist", lambda self, *a, **k: None, raising=False + ) + + log_query = build_query_logger() + log_query( + "postgresql://u@h:5432/analytics", + "SELECT id FROM public.orders", + "public", + "superset.sql_lab", + None, + {"sql_editor_id": "7"}, + ) + OpenLineageEventLogger().log( + user_id=1, action="execute_sql", records=[{}], duration_ms=50 + ) + + assert len(events) == 2 + job = events[0].job + assert job.namespace == "superset" + assert job.name == "sqllab.7" + assert any(d.name == "analytics.public.orders" for d in events[0].inputs) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py new file mode 100644 index 000000000..616627680 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py @@ -0,0 +1,94 @@ +# 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. +from typing import Any + +import superset_openlineage.event_logger as el_mod +from superset_openlineage.context import clear_pending, PendingRun, push_pending +from superset_openlineage.event_logger import OpenLineageEventLogger + + +def _pending() -> PendingRun: + return PendingRun( + run_id="11111111-1111-7111-8111-111111111111", + sql="SELECT id FROM public.orders", + dialect="postgres", + source="chart", + sql_hash="deadbeef", + schema="public", + catalog=None, + database="analytics", + username="alice", + sql_editor_id=None, + client_id=None, + start_time=0.0, + inputs=[], + outputs=[], + column_lineage=None, + ) + + +def test_execute_sql_emits_start_and_complete(monkeypatch: Any) -> None: + clear_pending() + push_pending(_pending()) + emitted = [] + monkeypatch.setattr(el_mod, "emit", lambda e: emitted.append(e)) + # do not touch the DB in the parent logger + monkeypatch.setattr( + OpenLineageEventLogger, "_persist", lambda self, *a, **k: None, raising=False + ) + + logger = OpenLineageEventLogger() + logger.log( + user_id=1, + action="execute_sql", + records=[{}], + dashboard_id=9, + slice_id=42, + duration_ms=120, + curated_payload={}, + curated_form_data={}, + ) + assert len(emitted) == 2 + assert emitted[0].job.name == "chart.42" + + +def test_non_execute_sql_emits_nothing(monkeypatch: Any) -> None: + clear_pending() + emitted = [] + monkeypatch.setattr(el_mod, "emit", lambda e: emitted.append(e)) + monkeypatch.setattr( + OpenLineageEventLogger, "_persist", lambda self, *a, **k: None, raising=False + ) + logger = OpenLineageEventLogger() + logger.log(user_id=1, action="DashboardRestApi.get", records=[{}]) + assert emitted == [] + + +def test_emit_failure_does_not_propagate(monkeypatch: Any) -> None: + clear_pending() + push_pending(_pending()) + + def boom(_e: Any) -> None: + raise RuntimeError("down") + + monkeypatch.setattr(el_mod, "emit", boom) + monkeypatch.setattr( + OpenLineageEventLogger, "_persist", lambda self, *a, **k: None, raising=False + ) + logger = OpenLineageEventLogger() + # must not raise + logger.log(user_id=1, action="execute_sql", records=[{}], slice_id=42) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py new file mode 100644 index 000000000..1095ec23b --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py @@ -0,0 +1,79 @@ +# 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. +from superset_openlineage.jobname import render_job_name, resolve_identity + + +def _vals(**over: object) -> dict[str, object]: + base: dict[str, object] = { + "source": "chart", + "sql_editor_id": "", + "client_id": "", + "slice_id": "", + "dashboard_id": "", + "sql_hash": "deadbeef", + "schema": "public", + "catalog": "", + "database": "analytics", + "username": "alice", + } + base.update(over) + return base + + +def test_identity_prefers_slice_id_for_chart() -> None: + assert resolve_identity(_vals(source="chart", slice_id="42")) == "42" + + +def test_identity_prefers_editor_then_client_for_sqllab() -> None: + assert ( + resolve_identity(_vals(source="sqllab", sql_editor_id="7", client_id="c1")) + == "7" + ) + assert ( + resolve_identity(_vals(source="sqllab", sql_editor_id="", client_id="c1")) + == "c1" + ) + + +def test_identity_falls_back_to_sql_hash() -> None: + assert resolve_identity(_vals(source="sqllab")) == "deadbeef" + + +def test_default_template() -> None: + vals = _vals(source="chart", slice_id="42") + vals["identity"] = resolve_identity(vals) + assert render_job_name("$source.$identity", vals) == "chart.42" + + +def test_missing_placeholder_collapses_separators() -> None: + vals = _vals(source="chart", slice_id="42", dashboard_id="") + vals["identity"] = resolve_identity(vals) + assert render_job_name("$source.$dashboard_id.$slice_id", vals) == "chart.42" + + +def test_unknown_placeholder_is_safe() -> None: + vals = _vals(source="chart", slice_id="42") + vals["identity"] = resolve_identity(vals) + # $nope is left untouched by safe_substitute, then normalized away only if empty; + # here it stays literal, proving no exception is raised. + assert "chart" in render_job_name("$source.$nope", vals) + + +def test_empty_result_falls_back_to_source_hash() -> None: + vals = _vals(source="chart", slice_id="", sql_hash="deadbeef") + vals["identity"] = resolve_identity(vals) + assert render_job_name("${slice_id}", vals) == "chart.deadbeef" diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py new file mode 100644 index 000000000..9f3ea28bb --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py @@ -0,0 +1,70 @@ +# 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. +from __future__ import annotations + +from typing import Any, Callable + +from superset_openlineage import context +from superset_openlineage.lifecycle import init_app + + +class FakeApp: + """Stand-in for a Flask app, capturing the registered teardown callback.""" + + def __init__(self) -> None: + self.handler: Callable[..., Any] | None = None + + def teardown_appcontext(self, f: Callable[..., Any]) -> Callable[..., Any]: + self.handler = f + return f + + +def _run(run_id: str) -> context.PendingRun: + return context.PendingRun( + run_id=run_id, + sql="SELECT 1", + dialect="postgres", + source="sqllab", + sql_hash="abc", + schema=None, + catalog=None, + database="d", + username="u", + sql_editor_id=None, + client_id=None, + start_time=0.0, + inputs=[], + outputs=[], + column_lineage=None, + ) + + +def test_init_app_registers_teardown_handler() -> None: + fake = FakeApp() + init_app(fake) + assert callable(fake.handler) + + +def test_teardown_handler_clears_pending_stash() -> None: + fake = FakeApp() + init_app(fake) + context.push_pending(_run("leaked")) + + assert fake.handler is not None + fake.handler(None) + + assert context.pop_pending() is None diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py new file mode 100644 index 000000000..0c89465f4 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py @@ -0,0 +1,92 @@ +# 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. +from __future__ import annotations + +from typing import Any, Iterator + +from sqlalchemy.engine import make_url +from sqlglot import exp + +import superset_openlineage.lineage as lineage_mod +from superset_openlineage.lineage import build_column_lineage_facet + + +class _FakeNode: + """Duck-typed stand-in for sqlglot.lineage.Node used to force an + intermediate (non-leaf) node whose expression resolves to a real + exp.Table, so we can prove it is excluded from the reported lineage.""" + + def __init__( + self, + name: str, + expression: exp.Expression | None = None, + downstream: list["_FakeNode"] | None = None, + ) -> None: + self.name = name + self.expression = expression + self.downstream = downstream or [] + + def walk(self) -> Iterator["_FakeNode"]: + yield self + for child in self.downstream: + yield from child.walk() + + +def test_column_lineage_maps_output_to_source() -> None: + url = make_url("postgresql://u@h:5432/analytics") + sql = "SELECT o.amount AS revenue FROM public.orders o" + facet = build_column_lineage_facet(sql, url, "postgres") + assert facet is not None + assert "revenue" in facet.fields + inputs = facet.fields["revenue"].inputFields + assert any( + i.name == "analytics.public.orders" and i.field == "amount" for i in inputs + ) + + +def test_column_lineage_bad_sql_returns_none() -> None: + url = make_url("postgresql://u@h:5432/analytics") + assert build_column_lineage_facet("not sql {{{", url, "postgres") is None + + +def test_column_lineage_skips_non_leaf_nodes(monkeypatch: Any) -> None: + # I5: an intermediate node that merely passes through a real table + # (i.e. it still has downstream nodes) must not be reported as a + # source column -- only the true leaf (no downstream) should be. + url = make_url("postgresql://u@h:5432/analytics") + + leaf_table = exp.Table( + this=exp.Identifier(this="orders"), db=exp.Identifier(this="public") + ) + mid_table = exp.Table( + this=exp.Identifier(this="staging"), db=exp.Identifier(this="public") + ) + leaf_node = _FakeNode(name="orders.amount", expression=leaf_table, downstream=[]) + mid_node = _FakeNode( + name="staging.amount", expression=mid_table, downstream=[leaf_node] + ) + root_node = _FakeNode(name="revenue", expression=None, downstream=[mid_node]) + + monkeypatch.setattr(lineage_mod, "sqlglot_lineage", lambda *a, **k: root_node) + + sql = "SELECT amount AS revenue FROM public.orders" + facet = build_column_lineage_facet(sql, url, "postgres") + assert facet is not None + inputs = facet.fields["revenue"].inputFields + names = {i.name for i in inputs} + assert names == {"analytics.public.orders"} + assert "analytics.public.staging" not in names diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py new file mode 100644 index 000000000..e27e68faa --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py @@ -0,0 +1,80 @@ +# 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. +from __future__ import annotations + +from openlineage.client.facet_v2 import column_lineage_dataset, schema_dataset +from sqlalchemy.engine import make_url + +from superset_openlineage.lineage import build_output_datasets + + +def test_ctas_produces_output_dataset_with_column_lineage() -> None: + url = make_url("postgresql://u@h:5432/analytics") + sql = ( + "CREATE TABLE analytics.public.summary AS " + "SELECT o.amount AS revenue FROM public.orders o" + ) + outputs = build_output_datasets(sql, url, "postgres") + assert len(outputs) == 1 + output = outputs[0] + assert output.name == "analytics.public.summary" + facets = output.facets + assert facets is not None + assert "columnLineage" in facets + column_lineage_facet = facets["columnLineage"] + assert isinstance( + column_lineage_facet, column_lineage_dataset.ColumnLineageDatasetFacet + ) + fields = column_lineage_facet.fields + assert "revenue" in fields + inputs = fields["revenue"].inputFields + assert any( + i.name == "analytics.public.orders" and i.field == "amount" for i in inputs + ) + assert "schema" in facets + schema_facet = facets["schema"] + assert isinstance(schema_facet, schema_dataset.SchemaDatasetFacet) + assert schema_facet.fields is not None + schema_fields = {f.name for f in schema_facet.fields} + assert schema_fields == {"revenue"} + + +def test_cvas_produces_output_dataset() -> None: + url = make_url("postgresql://u@h:5432/analytics") + sql = ( + "CREATE VIEW analytics.public.summary_v AS " + "SELECT o.amount AS revenue FROM public.orders o" + ) + outputs = build_output_datasets(sql, url, "postgres") + assert len(outputs) == 1 + assert outputs[0].name == "analytics.public.summary_v" + + +def test_plain_select_produces_no_output_dataset() -> None: + url = make_url("postgresql://u@h:5432/analytics") + sql = "SELECT id FROM public.orders" + assert build_output_datasets(sql, url, "postgres") == [] + + +def test_bad_sql_never_raises_and_returns_empty() -> None: + url = make_url("postgresql://u@h:5432/analytics") + assert build_output_datasets("not sql {{{", url, "postgres") == [] + + +def test_create_table_without_query_returns_empty() -> None: + url = make_url("postgresql://u@h:5432/analytics") + assert build_output_datasets("CREATE TABLE t (id int)", url, "postgres") == [] diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py new file mode 100644 index 000000000..6abcd2a3f --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py @@ -0,0 +1,47 @@ +# 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. +from sqlalchemy.engine import make_url + +from superset_openlineage.lineage import build_input_datasets, extract_tables +from superset_openlineage.naming import TableRef + + +def test_extract_tables_simple_join() -> None: + sql = "SELECT * FROM public.orders o JOIN public.customers c ON o.cid = c.id" + refs = set(extract_tables(sql, "postgres")) + assert TableRef(None, "public", "orders") in refs + assert TableRef(None, "public", "customers") in refs + + +def test_extract_tables_excludes_ctes() -> None: + sql = "WITH recent AS (SELECT * FROM public.orders) SELECT * FROM recent" + refs = extract_tables(sql, "postgres") + names = {r.table for r in refs} + assert "orders" in names + assert "recent" not in names + + +def test_extract_tables_bad_sql_returns_empty() -> None: + assert extract_tables("this is not sql {{{", "postgres") == [] + + +def test_build_input_datasets_names_and_namespaces() -> None: + url = make_url("postgresql://u@h:5432/analytics") + inputs = build_input_datasets("SELECT id FROM public.orders", url, "postgres") + assert len(inputs) == 1 + assert inputs[0].namespace == "postgres://h:5432" + assert inputs[0].name == "analytics.public.orders" diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py new file mode 100644 index 000000000..69d068704 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py @@ -0,0 +1,58 @@ +# 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. +from sqlalchemy.engine import make_url + +from superset_openlineage.naming import ( + dataset_name, + dataset_namespace, + sqlglot_dialect, + TableRef, +) + + +def test_postgres_namespace_and_name() -> None: + url = make_url("postgresql://u:p@db.example:5432/analytics") + assert dataset_namespace(url) == "postgres://db.example:5432" + ref = TableRef(catalog=None, schema="public", table="orders") + assert dataset_name(url, ref) == "analytics.public.orders" + + +def test_mysql_name_has_no_schema() -> None: + url = make_url("mysql://u:p@h:3306/sales") + ref = TableRef(catalog=None, schema=None, table="orders") + assert dataset_namespace(url) == "mysql://h:3306" + assert dataset_name(url, ref) == "sales.orders" + + +def test_trino_uses_catalog_schema_table() -> None: + url = make_url("trino://u@h:8080/hive") + ref = TableRef(catalog="hive", schema="default", table="events") + assert dataset_namespace(url) == "trino://h:8080" + assert dataset_name(url, ref) == "hive.default.events" + + +def test_generic_fallback() -> None: + url = make_url("somedb://u@h:1234/thedb") + ref = TableRef(catalog=None, schema="s", table="t") + assert dataset_namespace(url) == "somedb://h:1234" + assert dataset_name(url, ref) == "thedb.s.t" + + +def test_dialect_mapping() -> None: + assert sqlglot_dialect(make_url("postgresql://h/d")) == "postgres" + assert sqlglot_dialect(make_url("mysql://h/d")) == "mysql" + assert sqlglot_dialect(make_url("trino://h/d")) == "trino" diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py new file mode 100644 index 000000000..3fdba9753 --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py @@ -0,0 +1,41 @@ +# 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. +def test_openlineage_symbols_exist() -> None: + from openlineage.client import OpenLineageClient # noqa: F401 + from openlineage.client.event_v2 import ( # noqa: F401 + InputDataset, + Job, + OutputDataset, + Run, + RunEvent, + RunState, + ) + from openlineage.client.facet_v2 import ( # noqa: F401 + column_lineage_dataset, + job_type_job, + schema_dataset, + sql_job, + ) + from openlineage.client.uuid import generate_new_uuid + + assert str(generate_new_uuid()) + assert RunState.START + assert RunState.COMPLETE + + +def test_package_imports() -> None: + import superset_openlineage # noqa: F401 diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py new file mode 100644 index 000000000..735f98c8e --- /dev/null +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py @@ -0,0 +1,98 @@ +# 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. +from superset_openlineage.context import clear_pending, pop_pending +from superset_openlineage.query_logger import build_query_logger + + +def test_sqllab_source_pushes_pending_with_inputs() -> None: + clear_pending() + log = build_query_logger() + log( + "postgresql://u@h:5432/analytics", + "SELECT id FROM public.orders", + "public", + "superset.sql_lab", + None, + None, + ) + pending = pop_pending() + assert pending is not None + assert pending.source == "sqllab" + assert pending.run_id + assert any(d.name == "analytics.public.orders" for d in pending.inputs) + + +def test_chart_source_detected_from_client() -> None: + clear_pending() + log = build_query_logger() + log( + "postgresql://u@h:5432/analytics", + "SELECT 1", + None, + "superset.models.core", + ) + pending = pop_pending() + assert pending is not None + assert pending.source == "chart" + + +def test_ctas_pushes_pending_with_output_dataset() -> None: + clear_pending() + log = build_query_logger() + log( + "postgresql://u@h:5432/analytics", + "CREATE TABLE analytics.public.summary AS " + "SELECT o.amount AS revenue FROM public.orders o", + "public", + "superset.sql_lab", + None, + None, + ) + pending = pop_pending() + assert pending is not None + assert pending.column_lineage is None + assert len(pending.outputs) == 1 + output = pending.outputs[0] + assert output.name == "analytics.public.summary" + assert "columnLineage" in output.facets + + +def test_plain_select_pushes_pending_with_no_outputs() -> None: + clear_pending() + log = build_query_logger() + log( + "postgresql://u@h:5432/analytics", + "SELECT id FROM public.orders", + "public", + "superset.sql_lab", + None, + None, + ) + pending = pop_pending() + assert pending is not None + assert pending.outputs == [] + + +def test_bad_input_never_raises_and_pushes_nothing_useful() -> None: + clear_pending() + log = build_query_logger() + # a non-URL database value must not raise + log(None, "SELECT 1", None, "superset.sql_lab") + # either nothing pushed, or a pending with empty inputs — but no exception + pending = pop_pending() + if pending is not None: + assert pending.inputs == [] From 329cb75209a67673c9b1a2bdc3d99df4bf53070a Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:35:46 +0200 Subject: [PATCH 2/2] cleanup headers --- .../superset_openlineage/__init__.py | 16 ---------------- .../superset_openlineage/client.py | 16 ---------------- .../superset_openlineage/context.py | 16 ---------------- .../superset_openlineage/event_logger.py | 16 ---------------- .../superset_openlineage/facets.py | 16 ---------------- .../superset_openlineage/jobname.py | 16 ---------------- .../superset_openlineage/lifecycle.py | 16 ---------------- .../superset_openlineage/lineage.py | 16 ---------------- .../superset_openlineage/naming.py | 16 ---------------- .../superset_openlineage/query_logger.py | 16 ---------------- .../superset_openlineage/settings.py | 16 ---------------- .../superset_openlineage/tests/__init__.py | 16 ---------------- .../superset_openlineage/tests/test_client.py | 16 ---------------- .../superset_openlineage/tests/test_context.py | 16 ---------------- .../tests/test_end_to_end.py | 16 ---------------- .../tests/test_event_logger.py | 16 ---------------- .../superset_openlineage/tests/test_jobname.py | 16 ---------------- .../superset_openlineage/tests/test_lifecycle.py | 16 ---------------- .../tests/test_lineage_columns.py | 16 ---------------- .../tests/test_lineage_outputs.py | 16 ---------------- .../tests/test_lineage_tables.py | 16 ---------------- .../superset_openlineage/tests/test_naming.py | 16 ---------------- .../tests/test_openlineage_api.py | 16 ---------------- .../tests/test_query_logger.py | 16 ---------------- 24 files changed, 384 deletions(-) diff --git a/superset/stackable/superset-openlineage/superset_openlineage/__init__.py b/superset/stackable/superset-openlineage/superset_openlineage/__init__.py index 1e58bc0dd..3875b9097 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/__init__.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/__init__.py @@ -1,19 +1,3 @@ -# 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. """OpenLineage integration for Apache Superset (config-only plugin).""" __all__ = ["build_query_logger", "OpenLineageEventLogger", "init_app"] diff --git a/superset/stackable/superset-openlineage/superset_openlineage/client.py b/superset/stackable/superset-openlineage/superset_openlineage/client.py index 052f8911d..19708ffa6 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/client.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/client.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import logging diff --git a/superset/stackable/superset-openlineage/superset_openlineage/context.py b/superset/stackable/superset-openlineage/superset_openlineage/context.py index 1cd8e1b58..8f283925a 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/context.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/context.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations from contextvars import ContextVar diff --git a/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py index 3c8f40f54..bf58bbe2a 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/event_logger.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import logging diff --git a/superset/stackable/superset-openlineage/superset_openlineage/facets.py b/superset/stackable/superset-openlineage/superset_openlineage/facets.py index a4494c621..9b31069af 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/facets.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/facets.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import attr diff --git a/superset/stackable/superset-openlineage/superset_openlineage/jobname.py b/superset/stackable/superset-openlineage/superset_openlineage/jobname.py index a331a8412..36d6f68c3 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/jobname.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/jobname.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import re diff --git a/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py b/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py index 37bc19921..b61cf779c 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/lifecycle.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations from typing import Any diff --git a/superset/stackable/superset-openlineage/superset_openlineage/lineage.py b/superset/stackable/superset-openlineage/superset_openlineage/lineage.py index 341032c8b..0070eabb5 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/lineage.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/lineage.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import logging diff --git a/superset/stackable/superset-openlineage/superset_openlineage/naming.py b/superset/stackable/superset-openlineage/superset_openlineage/naming.py index 99b78398d..78239c612 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/naming.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/naming.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations from dataclasses import dataclass diff --git a/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py index ea17bc3de..4e6d105bf 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/query_logger.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import hashlib diff --git a/superset/stackable/superset-openlineage/superset_openlineage/settings.py b/superset/stackable/superset-openlineage/superset_openlineage/settings.py index 1d5f96d9d..276697733 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/settings.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/settings.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations import os diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py index 13a83393a..e69de29bb 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/__init__.py @@ -1,16 +0,0 @@ -# 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. diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py index a87df5709..4107e9b66 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_client.py @@ -1,19 +1,3 @@ -# 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. from typing import Any import superset_openlineage.client as client_mod diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py index 6e2238caa..341c65027 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_context.py @@ -1,19 +1,3 @@ -# 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. from superset_openlineage.context import ( clear_pending, PendingRun, diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py index fae7ae5f8..0f2c567e2 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_end_to_end.py @@ -1,19 +1,3 @@ -# 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. from typing import Any import superset_openlineage.client as client_mod diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py index 616627680..cee71286e 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_event_logger.py @@ -1,19 +1,3 @@ -# 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. from typing import Any import superset_openlineage.event_logger as el_mod diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py index 1095ec23b..852e5979b 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_jobname.py @@ -1,19 +1,3 @@ -# 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. from superset_openlineage.jobname import render_job_name, resolve_identity diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py index 9f3ea28bb..f5565fe4a 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lifecycle.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations from typing import Any, Callable diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py index 0c89465f4..9bc2e2b79 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_columns.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations from typing import Any, Iterator diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py index e27e68faa..c007d8bc0 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_outputs.py @@ -1,19 +1,3 @@ -# 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. from __future__ import annotations from openlineage.client.facet_v2 import column_lineage_dataset, schema_dataset diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py index 6abcd2a3f..54369de34 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_lineage_tables.py @@ -1,19 +1,3 @@ -# 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. from sqlalchemy.engine import make_url from superset_openlineage.lineage import build_input_datasets, extract_tables diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py index 69d068704..46d6176eb 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_naming.py @@ -1,19 +1,3 @@ -# 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. from sqlalchemy.engine import make_url from superset_openlineage.naming import ( diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py index 3fdba9753..2af227628 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_openlineage_api.py @@ -1,19 +1,3 @@ -# 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. def test_openlineage_symbols_exist() -> None: from openlineage.client import OpenLineageClient # noqa: F401 from openlineage.client.event_v2 import ( # noqa: F401 diff --git a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py index 735f98c8e..b94a54ab9 100644 --- a/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py +++ b/superset/stackable/superset-openlineage/superset_openlineage/tests/test_query_logger.py @@ -1,19 +1,3 @@ -# 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. from superset_openlineage.context import clear_pending, pop_pending from superset_openlineage.query_logger import build_query_logger