Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions superset/daos/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class ColumnOperatorEnum(str, Enum):
ne = "ne"
sw = "sw"
ew = "ew"
ct = "ct"
in_ = "in"
nin = "nin"
gt = "gt"
Expand All @@ -84,11 +85,12 @@ def apply(self, column: Any, value: Any) -> Any:
ColumnOperatorEnum.ne: lambda col, val: col != val,
ColumnOperatorEnum.sw: lambda col, val: col.like(f"{val}%"),
ColumnOperatorEnum.ew: lambda col, val: col.like(f"%{val}"),
ColumnOperatorEnum.ct: lambda col, val: col.ilike(f"%{val}%"),
ColumnOperatorEnum.in_: lambda col, val: col.in_(
val if isinstance(val, (list, tuple)) else [val]
),
ColumnOperatorEnum.nin: lambda col, val: ~col.in_(
val if isinstance(val, (list, tuple)) else [val]
ColumnOperatorEnum.nin: lambda col, val: (
~col.in_(val if isinstance(val, (list, tuple)) else [val])
),
ColumnOperatorEnum.gt: lambda col, val: col > val,
ColumnOperatorEnum.gte: lambda col, val: col >= val,
Expand All @@ -107,6 +109,7 @@ def apply(self, column: Any, value: Any) -> Any:
ColumnOperatorEnum.ne,
ColumnOperatorEnum.sw,
ColumnOperatorEnum.ew,
ColumnOperatorEnum.ct,
ColumnOperatorEnum.in_,
ColumnOperatorEnum.nin,
ColumnOperatorEnum.like,
Expand Down
4 changes: 4 additions & 0 deletions superset/mcp_service/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,10 @@ def create_mcp_app(
get_schema,
health_check,
)
from superset.mcp_service.tag.tool import ( # noqa: F401, E402
get_tag_info,
list_tags,
)


def _remove_disabled_tools(disabled_tools: set[str]) -> None:
Expand Down
17 changes: 3 additions & 14 deletions superset/mcp_service/chart/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from datetime import datetime
from typing import Annotated, Any, cast, Dict, List, Literal, Protocol

import humanize
from pydantic import (
AliasChoices,
AliasPath,
Expand Down Expand Up @@ -61,6 +60,7 @@
escape_llm_context_delimiters,
sanitize_for_llm_context,
)
from superset.mcp_service.utils.response_utils import humanize_timestamp
from superset.mcp_service.utils.sanitization import (
sanitize_filter_value,
sanitize_user_input,
Expand Down Expand Up @@ -298,13 +298,6 @@ def validate_identifier_or_form_data_key(self) -> "GetChartInfoRequest":
return self


def _humanize_timestamp(dt: datetime | None) -> str | None:
"""Convert a datetime to a humanized string like '2 hours ago'."""
if dt is None:
return None
return humanize.naturaltime(datetime.now() - dt)


def extract_filters_from_form_data(
form_data: Dict[str, Any] | None,
) -> ChartFiltersInfo | None:
Expand Down Expand Up @@ -504,13 +497,9 @@ def serialize_chart_object(chart: ChartLike | None) -> ChartInfo | None:
form_data=chart_form_data,
filters=filters_info,
changed_on=getattr(chart, "changed_on", None),
changed_on_humanized=_humanize_timestamp(
getattr(chart, "changed_on", None)
),
changed_on_humanized=humanize_timestamp(getattr(chart, "changed_on", None)),
created_on=getattr(chart, "created_on", None),
created_on_humanized=_humanize_timestamp(
getattr(chart, "created_on", None)
),
created_on_humanized=humanize_timestamp(getattr(chart, "created_on", None)),
uuid=str(getattr(chart, "uuid", ""))
if getattr(chart, "uuid", None)
else None,
Expand Down
13 changes: 3 additions & 10 deletions superset/mcp_service/dashboard/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
from datetime import datetime
from typing import Annotated, Any, cast, Dict, List, Literal, TYPE_CHECKING

import humanize
from pydantic import (
AliasChoices,
BaseModel,
Expand Down Expand Up @@ -104,6 +103,7 @@
escape_llm_context_delimiters,
sanitize_for_llm_context,
)
from superset.mcp_service.utils.response_utils import humanize_timestamp
from superset.mcp_service.utils.sanitization import (
sanitize_user_input,
sanitize_user_input_with_changes,
Expand Down Expand Up @@ -904,13 +904,6 @@ def dashboard_serializer(dashboard: "Dashboard") -> DashboardInfo:
)


def _humanize_timestamp(dt: datetime | None) -> str | None:
"""Convert a datetime to a humanized string like '2 hours ago'."""
if dt is None:
return None
return humanize.naturaltime(datetime.now() - dt)


def serialize_dashboard_object(dashboard: Any) -> DashboardInfo:
"""Simple dashboard serializer that safely handles object attributes."""
from superset.mcp_service.utils.url_utils import get_superset_base_url
Expand All @@ -937,11 +930,11 @@ def serialize_dashboard_object(dashboard: Any) -> DashboardInfo:
url=dashboard_url,
published=getattr(dashboard, "published", None),
changed_on=getattr(dashboard, "changed_on", None),
changed_on_humanized=_humanize_timestamp(
changed_on_humanized=humanize_timestamp(
getattr(dashboard, "changed_on", None)
),
created_on=getattr(dashboard, "created_on", None),
created_on_humanized=_humanize_timestamp(
created_on_humanized=humanize_timestamp(
getattr(dashboard, "created_on", None)
),
description=getattr(dashboard, "description", None),
Expand Down
6 changes: 3 additions & 3 deletions superset/mcp_service/dashboard/tool/generate_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,10 @@ def generate_dashboard( # noqa: C901

# Convert to our response format
from superset.mcp_service.dashboard.schemas import (
_humanize_timestamp,
serialize_chart_summary,
serialize_tag_object,
)
from superset.mcp_service.utils.response_utils import humanize_timestamp

include_data_model_metadata = user_can_view_data_model_metadata()
dashboard_info = DashboardInfo(
Expand All @@ -428,8 +428,8 @@ def generate_dashboard( # noqa: C901
published=dashboard.published,
created_on=dashboard.created_on,
changed_on=dashboard.changed_on,
created_on_humanized=_humanize_timestamp(dashboard.created_on),
changed_on_humanized=_humanize_timestamp(dashboard.changed_on),
created_on_humanized=humanize_timestamp(dashboard.created_on),
changed_on_humanized=humanize_timestamp(dashboard.changed_on),
created_by=dashboard.created_by_name or None,
changed_by=dashboard.changed_by_name or None,
uuid=str(dashboard.uuid) if dashboard.uuid else None,
Expand Down
14 changes: 3 additions & 11 deletions superset/mcp_service/database/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from datetime import datetime
from typing import Annotated, Any, cast, Dict, List, Literal

import humanize
from pydantic import (
BaseModel,
ConfigDict,
Expand All @@ -43,6 +42,7 @@
from superset.mcp_service.constants import DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE
from superset.mcp_service.privacy import filter_user_directory_fields
from superset.mcp_service.system.schemas import PaginationInfo
from superset.mcp_service.utils.response_utils import humanize_timestamp
from superset.mcp_service.utils.schema_utils import (
parse_json_or_list,
parse_json_or_model_list,
Expand Down Expand Up @@ -305,14 +305,6 @@ def _parse_json_field(obj: Any, field_name: str) -> Dict[str, Any] | None:
return value


def _humanize_timestamp(dt: datetime | None) -> str | None:
"""Convert a datetime to a humanized string like '2 hours ago'."""
if dt is None:
return None
now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now()
return humanize.naturaltime(now - dt)


def _get_backend(database: Any) -> str | None:
"""Safely get backend from a Database object or row proxy.

Expand Down Expand Up @@ -351,7 +343,7 @@ def serialize_database_object(database: Any) -> DatabaseInfo | None:
external_url=getattr(database, "external_url", None),
extra=_parse_json_field(database, "extra"),
changed_on=getattr(database, "changed_on", None),
changed_on_humanized=_humanize_timestamp(getattr(database, "changed_on", None)),
changed_on_humanized=humanize_timestamp(getattr(database, "changed_on", None)),
created_on=getattr(database, "created_on", None),
created_on_humanized=_humanize_timestamp(getattr(database, "created_on", None)),
created_on_humanized=humanize_timestamp(getattr(database, "created_on", None)),
)
13 changes: 3 additions & 10 deletions superset/mcp_service/dataset/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from datetime import datetime
from typing import Annotated, Any, Dict, List, Literal

import humanize
from pydantic import (
BaseModel,
ConfigDict,
Expand Down Expand Up @@ -54,6 +53,7 @@
escape_llm_context_delimiters,
sanitize_for_llm_context,
)
from superset.mcp_service.utils.response_utils import humanize_timestamp
from superset.utils import json


Expand Down Expand Up @@ -554,13 +554,6 @@ def _parse_json_field(obj: Any, field_name: str) -> Dict[str, Any] | None:
return value


def _humanize_timestamp(dt: datetime | None) -> str | None:
"""Convert a datetime to a humanized string like '2 hours ago'."""
if dt is None:
return None
return humanize.naturaltime(datetime.now() - dt)


def _sanitize_dataset_info_for_llm_context(dataset_info: DatasetInfo) -> DatasetInfo:
"""Wrap dataset read-path descriptive fields before LLM exposure."""
payload = dataset_info.model_dump(mode="python")
Expand Down Expand Up @@ -691,11 +684,11 @@ def serialize_dataset_object(dataset: Any) -> DatasetInfo | None:
certified_by=getattr(dataset, "certified_by", None),
certification_details=getattr(dataset, "certification_details", None),
changed_on=getattr(dataset, "changed_on", None),
changed_on_humanized=_humanize_timestamp(
changed_on_humanized=humanize_timestamp(
getattr(dataset, "changed_on", None)
),
created_on=getattr(dataset, "created_on", None),
created_on_humanized=_humanize_timestamp(
created_on_humanized=humanize_timestamp(
getattr(dataset, "created_on", None)
),
tags=[
Expand Down
16 changes: 16 additions & 0 deletions superset/mcp_service/tag/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading