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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/scripts/get_scm_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from setuptools_scm import get_version

version = get_version(root='../../', relative_to=__file__)
print(version.split('+')[0])
version = get_version(root="../../", relative_to=__file__)
print(version.split("+")[0])
59 changes: 28 additions & 31 deletions benchmarks/lsp_render_model_bench.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,45 @@
#!/usr/bin/env python

import asyncio
import pyperf
import os
import logging
import os
from pathlib import Path

import pyperf
from lsprotocol import types
from pygls.client import JsonRPCClient

from sqlmesh.lsp.custom import RenderModelRequest, RENDER_MODEL_FEATURE
from sqlmesh.lsp.custom import RENDER_MODEL_FEATURE, RenderModelRequest
from sqlmesh.lsp.uri import URI
from pygls.client import JsonRPCClient

# Suppress debug logging during benchmark
logging.getLogger().setLevel(logging.WARNING)


class LSPClient(JsonRPCClient):
"""A custom LSP client for benchmarking."""

def __init__(self):
super().__init__()
self.render_model_result = None
self.initialized = asyncio.Event()

# Register handlers for notifications we expect from the server
@self.feature(types.WINDOW_SHOW_MESSAGE)
def handle_show_message(_):
# Silently ignore show message notifications during benchmark
pass

@self.feature(types.WINDOW_LOG_MESSAGE)
def handle_log_message(_):
# Silently ignore log message notifications during benchmark
pass

async def initialize_server(self):
"""Send initialization request to server."""
# Get the sushi example directory
sushi_dir = Path(__file__).parent.parent / "examples" / "sushi"

response = await self.protocol.send_request_async(
types.INITIALIZE,
types.InitializeParams(
Expand All @@ -47,13 +48,12 @@ async def initialize_server(self):
capabilities=types.ClientCapabilities(),
workspace_folders=[
types.WorkspaceFolder(
uri=URI.from_path(sushi_dir).value,
name="sushi"
uri=URI.from_path(sushi_dir).value, name="sushi"
)
]
)
],
),
)

# Send initialized notification
self.protocol.notify(types.INITIALIZED, types.InitializedParams())
self.initialized.set()
Expand All @@ -63,56 +63,53 @@ async def initialize_server(self):
async def benchmark_render_model_async(client: LSPClient, model_path: Path):
"""Benchmark the render_model request."""
uri = URI.from_path(model_path).value

# Send render_model request
result = await client.protocol.send_request_async(
RENDER_MODEL_FEATURE,
RenderModelRequest(textDocumentUri=uri)
RENDER_MODEL_FEATURE, RenderModelRequest(textDocumentUri=uri)
)

return result


def benchmark_render_model(loops):
"""Synchronous wrapper for the benchmark."""

async def run():
# Create client
client = LSPClient()

# Start the SQLMesh LSP server as a subprocess
await client.start_io("python", "-m", "sqlmesh.lsp.main")

# Initialize the server
await client.initialize_server()

# Get a model file to test with
sushi_dir = Path(__file__).parent.parent / "examples" / "sushi"
model_path = sushi_dir / "models" / "customers.sql"

# Warm up
await benchmark_render_model_async(client, model_path)

# Run benchmark
t0 = pyperf.perf_counter()
for _ in range(loops):
await benchmark_render_model_async(client, model_path)
dt = pyperf.perf_counter() - t0

# Clean up
await client.stop()

return dt

return asyncio.run(run())


def main():
runner = pyperf.Runner()
runner.bench_time_func(
"lsp_render_model",
benchmark_render_model
)
runner.bench_time_func("lsp_render_model", benchmark_render_model)


if __name__ == "__main__":
main()
main()
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import typing as t

from sqlmesh import CustomMaterialization, CustomKind, Model
from sqlmesh import CustomKind, CustomMaterialization, Model
from sqlmesh.utils.pydantic import validate_string

if t.TYPE_CHECKING:
Expand All @@ -15,7 +15,9 @@ def custom_property(self) -> str:
return validate_string(self.materialization_properties.get("custom_property"))


class CustomFullWithCustomKindMaterialization(CustomMaterialization[ExtendedCustomKind]):
class CustomFullWithCustomKindMaterialization(
CustomMaterialization[ExtendedCustomKind]
):
NAME = "custom_full_with_custom_kind"

def insert(
Expand Down
38 changes: 19 additions & 19 deletions examples/sushi/config.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import os

from sqlmesh.core.config.common import VirtualEnvironmentMode, TableNamingConvention
from sqlmesh.core.config import (
AutoCategorizationMode,
BigQueryConnectionConfig,
CategorizerConfig,
Config,
DuckDBConnectionConfig,
EnvironmentSuffixTarget,
GatewayConfig,
ModelDefaultsConfig,
PlanConfig,
)
from sqlmesh.core.config import (AutoCategorizationMode,
BigQueryConnectionConfig, CategorizerConfig,
Config, DuckDBConnectionConfig,
EnvironmentSuffixTarget, GatewayConfig,
ModelDefaultsConfig, PlanConfig)
from sqlmesh.core.config.common import (TableNamingConvention,
VirtualEnvironmentMode)
from sqlmesh.core.config.linter import LinterConfig
from sqlmesh.core.notification_target import (
BasicSMTPNotificationTarget,
SlackApiNotificationTarget,
SlackWebhookNotificationTarget,
)
from sqlmesh.core.notification_target import (BasicSMTPNotificationTarget,
SlackApiNotificationTarget,
SlackWebhookNotificationTarget)
from sqlmesh.core.user import User, UserRole

CURRENT_FILE_PATH = os.path.abspath(__file__)
Expand Down Expand Up @@ -64,7 +57,9 @@
gateways={
"bq": GatewayConfig(
connection=BigQueryConnectionConfig(),
state_connection=DuckDBConnectionConfig(database=f"{DATA_DIR}/bigquery.duckdb"),
state_connection=DuckDBConnectionConfig(
database=f"{DATA_DIR}/bigquery.duckdb"
),
)
},
default_gateway="bq",
Expand Down Expand Up @@ -123,7 +118,12 @@
roles=[UserRole.REQUIRED_APPROVER],
notification_targets=[
SlackApiNotificationTarget(
notify_on=["apply_start", "apply_failure", "apply_end", "audit_failure"],
notify_on=[
"apply_start",
"apply_failure",
"apply_end",
"audit_failure",
],
token=os.getenv("ADMIN_SLACK_API_TOKEN"),
channel="UXXXXXXXXX", # User's Slack member ID
),
Expand Down
8 changes: 6 additions & 2 deletions examples/sushi/macros/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@

@macro()
def incremental_by_ds(evaluator, column: exp.Column):
return between(evaluator, column, evaluator.locals["start_date"], evaluator.locals["end_date"])
return between(
evaluator, column, evaluator.locals["start_date"], evaluator.locals["end_date"]
)


@macro()
def assert_has_columns(evaluator, model, columns_to_types):
if evaluator.runtime_stage == "creating":
expected_schema = {
column_type.name: exp.maybe_parse(
column_type.text("expression"), into=exp.DataType, dialect=evaluator.dialect
column_type.text("expression"),
into=exp.DataType,
dialect=evaluator.dialect,
)
for column_type in columns_to_types.expressions
}
Expand Down
1 change: 1 addition & 0 deletions examples/sushi/models/disabled.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing as t

from sqlmesh import ExecutionContext, model


Expand Down
4 changes: 3 additions & 1 deletion examples/sushi/models/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
@model(
"sushi.items",
kind=dict(
name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, time_column="event_date", batch_size=30
name=ModelKindName.INCREMENTAL_BY_TIME_RANGE,
time_column="event_date",
batch_size=30,
),
start="1 week ago",
cron="@daily",
Expand Down
6 changes: 5 additions & 1 deletion examples/sushi/models/order_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ def get_items_table(context: ExecutionContext) -> str:
audits=[
(
"NOT_NULL",
{"columns": [to_column(c) for c in ("id", "order_id", "item_id", "quantity")]},
{
"columns": [
to_column(c) for c in ("id", "order_id", "item_id", "quantity")
]
},
),
("assert_order_items_quantity_exceeds_threshold", {"quantity": 0}),
],
Expand Down
4 changes: 3 additions & 1 deletion examples/sushi/models/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"sushi.orders",
description="Table of sushi orders.",
kind=dict(
name=ModelKindName.INCREMENTAL_BY_TIME_RANGE, time_column="event_date", batch_size=30
name=ModelKindName.INCREMENTAL_BY_TIME_RANGE,
time_column="event_date",
batch_size=30,
),
start="1 week ago",
cron="@daily",
Expand Down
8 changes: 6 additions & 2 deletions examples/sushi/models/raw_marketing.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,18 @@ def execute(
df_new = pd.DataFrame(
{
"customer_id": random.sample(range(0, 100), k=num_customers),
"status": np.random.choice(["active", "inactive"], size=num_customers, p=[0.8, 0.2]),
"status": np.random.choice(
["active", "inactive"], size=num_customers, p=[0.8, 0.2]
),
"updated_at": [exec_time] * num_customers,
}
)

# clickhouse returns a dataframe with no columns if the query is empty, so we can't merge
if not df_existing.empty:
df = df_new.merge(df_existing, on="customer_id", how="left", suffixes=(None, "_old"))
df = df_new.merge(
df_existing, on="customer_id", how="left", suffixes=(None, "_old")
)
else:
df = df_new
df["status_old"] = pd.NA
Expand Down
4 changes: 3 additions & 1 deletion examples/sushi/models/waiters.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def entrypoint(evaluator: MacroEvaluator) -> exp.Select:

name = ".".join([f'"{default_catalog}"', name])

assert parent_snapshot_name == name, f"Snapshot Name: {parent_snapshot_name}, Name: {name}"
assert (
parent_snapshot_name == name
), f"Snapshot Name: {parent_snapshot_name}, Name: {name}"

excluded = {"id", "customer_id", "start_ts", "end_ts"}
projections = []
Expand Down
2 changes: 1 addition & 1 deletion examples/sushi/signals/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import typing as t

from sqlmesh import signal, DatetimeRanges
from sqlmesh import DatetimeRanges, signal


@signal()
Expand Down
8 changes: 7 additions & 1 deletion examples/sushi_dlt/sushi_pipeline.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing as t

import dlt


Expand Down Expand Up @@ -76,7 +77,12 @@ def sushi_menu() -> t.Iterator[t.Dict[str, t.Any]]:
{
"id": 3,
"name": "Temaki",
"fillings": ["Tuna Temaki", "Salmon Temaki", "Vegetable Temaki", "Ebi Temaki"],
"fillings": [
"Tuna Temaki",
"Salmon Temaki",
"Vegetable Temaki",
"Ebi Temaki",
],
"details": {
"preparation": "Hand Roll",
"ingredients": ["Seaweed", "Rice", "Fish", "Vegetables"],
Expand Down
10 changes: 5 additions & 5 deletions examples/wursthall/models/db/order_f.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ def execute(
)

df_order_item_f = context.fetchdf(
parse_one(
f"""
parse_one(f"""
SELECT
order_id,
customer_id,
Expand All @@ -64,14 +63,15 @@ def execute(
FROM {order_item_f_table_name}
WHERE
order_ds BETWEEN '{to_ds(start)}' AND '{to_ds(end)}'
"""
),
"""),
quote_identifiers=True,
)

df_order_item_f = df_order_item_f.merge(df_item_d, how="inner", on="item_id")
df_order_item_f["item_price"] = 1.00
df_order_item_f["item_total"] = df_order_item_f["item_price"] * df_order_item_f["quantity"]
df_order_item_f["item_total"] = (
df_order_item_f["item_price"] * df_order_item_f["quantity"]
)
df_order_item_f = (
df_order_item_f.groupby(["order_id", "customer_id", "order_ds"], dropna=False)
.agg(order_total=("item_total", "sum"))
Expand Down
3 changes: 2 additions & 1 deletion examples/wursthall/models/src/customer_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import pandas as pd # noqa: TID253
from faker import Faker
from models.src.shared import DATA_START_DATE_STR, iter_dates, set_seed # type: ignore
from models.src.shared import (DATA_START_DATE_STR, iter_dates, # type: ignore
set_seed)

from sqlmesh import model
from sqlmesh.core.model import IncrementalByTimeRangeKind, TimeColumn
Expand Down
Loading