Skip to content

Commit d3839b3

Browse files
committed
test [ci skip]
1 parent 95794e5 commit d3839b3

15 files changed

Lines changed: 215 additions & 334 deletions

File tree

sqlmesh/cli/main.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -772,17 +772,6 @@ def ui(ctx: click.Context, host: str, port: int, mode: str) -> None:
772772
)
773773

774774

775-
@cli.command("lsp")
776-
@click.pass_obj
777-
@error_handler
778-
@cli_analytics
779-
def lsp(obj: Context) -> None:
780-
"""Start a language server for SQLMesh."""
781-
from lsp.main import server
782-
783-
server.start_io()
784-
785-
786775
@cli.command("migrate")
787776
@click.pass_context
788777
@error_handler

sqlmesh/core/context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,11 @@ def format(
11371137
return False
11381138

11391139
return True
1140+
1141+
1142+
1143+
1144+
11401145

11411146
@python_api_analytics
11421147
def plan(

sqlmesh/lsp/main.py

Lines changed: 15 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,34 @@
11
#!/usr/bin/env python
22
"""A Language Server Protocol (LSP) server for SQL with SQLMesh integration."""
33

4-
import asyncio
5-
import gc
64
import logging
75
import typing as t
86
import weakref
9-
from collections import defaultdict
107
from contextlib import suppress
11-
from itertools import cycle
128
from pathlib import Path
139

14-
import sqlmesh
10+
from sqlmesh.core.context import Context
1511
from lsprotocol import types
1612
from pygls.server import LanguageServer
1713
from pygls.workspace import TextDocument
1814
from sqlmesh._version import __version__
15+
from sqlmesh.core.model import Model
1916

2017
logger = logging.getLogger(__name__)
2118

22-
CONTEXTS: t.Dict[str, sqlmesh.Context] = {}
19+
CONTEXTS: t.Dict[str, Context] = {}
2320
"""A mapping of workspace paths to SQLMesh contexts."""
2421

25-
PATHS_TO_MODELS: t.Dict[str, t.Tuple[sqlmesh.Context, sqlmesh.Model]] = {}
22+
PATHS_TO_MODELS: t.Dict[str, t.Tuple[Context, Model]] = {}
2623
"""A mapping of file paths to SQLMesh (context, model) tuples."""
2724

28-
C_MUTEX: t.DefaultDict[t.Union[str, Path], asyncio.Lock] = defaultdict(asyncio.Lock)
29-
"""A locking mechanism for ensuring that context mutation is thread-safe."""
25+
server = LanguageServer("sqlmesh_lsp", __version__)
3026

31-
loop = asyncio.get_event_loop()
32-
33-
server = LanguageServer("sqlmesh_lsp", __version__, loop=loop)
34-
35-
36-
async def refresh_context_loop(context: sqlmesh.Context) -> None:
37-
"""Refresh the SQLMesh context every 5 seconds.
38-
SQLMesh already ensures that the context is only refreshed when necessary so this
39-
is efficient and safe to do, even if the context is large. Mtime checks are used.
40-
"""
41-
gc_iter = cycle(list(range(10)))
42-
while True:
43-
await asyncio.sleep(10.0)
44-
for loader in context._loaders:
45-
if loader.reload_needed():
46-
async with C_MUTEX[context.path]:
47-
await asyncio.to_thread(context.load)
48-
PATHS_TO_MODELS.update(
49-
{
50-
str(model._path.resolve()): (context, weakref.proxy(model))
51-
for model in context.models.values()
52-
}
53-
)
54-
if next(gc_iter) == 0:
55-
gc.collect()
56-
57-
58-
_CACHE = set()
27+
_CACHE: t.Set[str] = set()
5928
"""A cache of URIs for which we have already ensured a context exists."""
6029

6130

62-
async def ensure_context_for_document(document: TextDocument) -> TextDocument:
31+
def ensure_context_for_document(document: TextDocument) -> TextDocument:
6332
"""Ensure that a context exists for the given document if applicable."""
6433
if document.uri in _CACHE:
6534
return document
@@ -79,8 +48,7 @@ async def ensure_context_for_document(document: TextDocument) -> TextDocument:
7948
config_path = path / f"config.{ext}"
8049
if config_path.exists():
8150
with suppress(Exception):
82-
handle = sqlmesh.Context(paths=path)
83-
loop.create_task(refresh_context_loop(handle))
51+
handle = Context(paths=[f"{path}"])
8452
CONTEXTS[str(path)] = handle
8553
PATHS_TO_MODELS.update(
8654
{
@@ -96,17 +64,18 @@ async def ensure_context_for_document(document: TextDocument) -> TextDocument:
9664

9765

9866
@server.feature(types.TEXT_DOCUMENT_FORMATTING)
99-
async def formatting(
67+
def formatting(
10068
ls: LanguageServer, params: types.DocumentFormattingParams
10169
) -> t.List[types.TextEdit]:
10270
"""Format the document based using SQLMesh format_model_expressions."""
10371
try:
10472
logger.info(f"Formatting document: {params.text_document.uri}")
105-
document = await ensure_context_for_document(
106-
ls.workspace.get_document(params.text_document.uri)
107-
)
108-
context, model = PATHS_TO_MODELS.get(document.path, (None, None))
109-
context.format(paths=[Path(document.path)])
73+
document = ensure_context_for_document(ls.workspace.get_document(params.text_document.uri))
74+
context, _ = PATHS_TO_MODELS.get(document.path, (None, None))
75+
if context is None:
76+
logger.error(f"No context found for document: {document.path}")
77+
return []
78+
context.format(paths=(Path(document.path),))
11079
with open(document.path, "r+", encoding="utf-8") as file:
11180
return [
11281
types.TextEdit(
@@ -122,84 +91,6 @@ async def formatting(
12291
return []
12392

12493

125-
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
126-
async def did_open(ls: LanguageServer, params: types.DidOpenTextDocumentParams) -> None:
127-
"""Update diagnostics on document open and refresh context if necessary."""
128-
document = await ensure_context_for_document(
129-
ls.workspace.get_document(params.text_document.uri)
130-
)
131-
path = Path(document.path)
132-
known_paths = PATHS_TO_MODELS.keys()
133-
for context in CONTEXTS.values():
134-
if (
135-
path.is_relative_to(context.path)
136-
and path.suffix in (".sql", ".py")
137-
and str(path) not in known_paths
138-
):
139-
ls.show_message(f"Refreshing context with new file: {path}", types.MessageType.Info)
140-
async with C_MUTEX[context.path]:
141-
await asyncio.to_thread(context.load)
142-
PATHS_TO_MODELS.update(
143-
{
144-
str(model._path.resolve()): (context, weakref.proxy(model))
145-
for model in context.models.values()
146-
}
147-
)
148-
149-
150-
@server.feature(types.TEXT_DOCUMENT_DID_SAVE)
151-
async def did_save(ls: LanguageServer, params: types.DidOpenTextDocumentParams) -> None:
152-
"""Update diagnostics on document save."""
153-
document = await ensure_context_for_document(
154-
ls.workspace.get_document(params.text_document.uri)
155-
)
156-
context, _ = PATHS_TO_MODELS.get(document.path, (None, None))
157-
if context is not None:
158-
for loader in context._loaders:
159-
loader._path_mtimes[Path(document.path)] = 0.0
160-
async with C_MUTEX[context.path]:
161-
await asyncio.to_thread(context.load)
162-
for model in context.models.values():
163-
if model._path == Path(document.path):
164-
PATHS_TO_MODELS[document.path] = (context, weakref.proxy(model))
165-
break
166-
167-
168-
@server.feature(types.WORKSPACE_DID_CHANGE_WATCHED_FILES)
169-
async def did_change_watched_files(
170-
ls: LanguageServer, params: types.DidChangeWatchedFilesParams
171-
) -> None:
172-
"""Refresh context if a file changes."""
173-
updated: t.Dict[t.Union[str, Path], bool] = {}
174-
for change in params.changes:
175-
document = await ensure_context_for_document(ls.workspace.get_text_document(change.uri))
176-
path = Path(document.path)
177-
known_paths = PATHS_TO_MODELS.keys()
178-
if change.type == types.FileChangeType.Deleted and str(path) in known_paths:
179-
# We don't need to refresh the context if a file is deleted, we just remove it from the cache
180-
del PATHS_TO_MODELS[str(path)]
181-
continue
182-
for context in CONTEXTS.values():
183-
# If a new file is created, we need to force reload the appropriate context
184-
if (
185-
path.is_relative_to(context.path)
186-
and path.suffix in (".sql", ".py")
187-
and str(path) not in known_paths
188-
and change.type == types.FileChangeType.Created
189-
and not updated.get(context.path, False)
190-
):
191-
ls.show_message(f"Refreshing context with new file: {path}", types.MessageType.Info)
192-
async with C_MUTEX[context.path]:
193-
await asyncio.to_thread(context.load)
194-
PATHS_TO_MODELS.update(
195-
{
196-
str(model._path.resolve()): (context, weakref.proxy(model))
197-
for model in context.models.values()
198-
}
199-
)
200-
updated[context.path] = True
201-
202-
20394
def main() -> None:
20495
logging.basicConfig(level=logging.DEBUG)
20596
server.start_io()

web/client/public/css/design.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,7 @@
439439
--shadow-l: 0 var(--step) calc(var(--half) * 4) var(--color-shadow-10);
440440
--shadow-xl: 0 var(--step) calc(var(--half) * 5) var(--color-shadow-10);
441441

442-
--tooltip-shadow:
443-
0 var(--step-2) calc(var(--step) * 4) -1px var(--color-shadow-30),
442+
--tooltip-shadow: 0 var(--step-2) calc(var(--step) * 4) -1px var(--color-shadow-30),
444443
inset 0 0 0 1px var(--color-gray-300);
445444
--tooltip-background: var(--color-light);
446445
--tooltip-text: var(--color-gray-600);

web/client/src/library/components/editor/hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ function useSQLMeshModelExtensions(
9999
m.columns.map(c => c.name),
100100
)
101101
: (Object.keys(lineage)
102-
.flatMap(modelName =>
103-
models.get(modelName)?.columns.map(c => c.name),
102+
.flatMap(
103+
modelName => models.get(modelName)?.columns.map(c => c.name),
104104
)
105105
.filter(Boolean) as string[]),
106106
)

web/client/src/library/components/fileExplorer/FileExplorer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,10 @@ function FileExplorerArtifactContainer({
270270
isTopGroupInActiveRange(artifact)
271271
? 'rounded-t-md'
272272
: isBottomGroupInActiveRange(artifact)
273-
? 'rounded-b-md'
274-
: isMiddleGroupInActiveRange(artifact)
275-
? ''
276-
: inActiveRange(artifact) && 'rounded-md',
273+
? 'rounded-b-md'
274+
: isMiddleGroupInActiveRange(artifact)
275+
? ''
276+
: inActiveRange(artifact) && 'rounded-md',
277277
inActiveRange(artifact) &&
278278
activeRange.length > 1 &&
279279
activeRange.indexOf(artifact) < activeRange.length - 1

web/client/src/library/components/graph/ModelColumns.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default function ModelColumns({
111111
if (isNil(lineageCache)) {
112112
const mainNodeLineage = isNil(mainNode)
113113
? undefined
114-
: (lineage[mainNode] ?? lineageCache?.[mainNode])
114+
: lineage[mainNode] ?? lineageCache?.[mainNode]
115115

116116
newLineageCache = lineage
117117
currentConnections = new Map()
@@ -471,10 +471,10 @@ function ModelColumn({
471471
isError
472472
? 'text-danger-500'
473473
: isTimeout
474-
? 'text-warning-500'
475-
: isEmpty
476-
? 'text-neutral-400 dark:text-neutral-600'
477-
: 'text-prose',
474+
? 'text-warning-500'
475+
: isEmpty
476+
? 'text-neutral-400 dark:text-neutral-600'
477+
: 'text-prose',
478478
)}
479479
/>
480480
</ColumnHandles>
@@ -495,10 +495,10 @@ function ModelColumn({
495495
isError
496496
? 'text-danger-500'
497497
: isTimeout
498-
? 'text-warning-500'
499-
: isEmpty
500-
? 'text-neutral-400 dark:text-neutral-600'
501-
: 'text-prose',
498+
? 'text-warning-500'
499+
: isEmpty
500+
? 'text-neutral-400 dark:text-neutral-600'
501+
: 'text-prose',
502502
)}
503503
/>
504504
</>

web/client/src/library/components/graph/ModelNode.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,20 +166,20 @@ export default function ModelNode({
166166
isCTE
167167
? 'border-accent-500 bg-accent-500 text-accent-500 dark:border-accent-300 dark:bg-accent-300 dark:text-accent-300'
168168
: isModelUnknown
169-
? 'border-neutral-500 bg-neutral-500 text-neutral-500 dark:border-neutral-300 dark:bg-neutral-300 dark:text-neutral-300'
170-
: 'border-secondary-500 bg-secondary-500 text-secondary-500 dark:bg-primary-500 dark:border-primary-500 dark:text-primary-500',
169+
? 'border-neutral-500 bg-neutral-500 text-neutral-500 dark:border-neutral-300 dark:bg-neutral-300 dark:text-neutral-300'
170+
: 'border-secondary-500 bg-secondary-500 text-secondary-500 dark:bg-primary-500 dark:border-primary-500 dark:text-primary-500',
171171
isMainNode
172172
? 'ring-8 ring-brand-50'
173173
: isModelExternal || isModelSeed
174-
? 'ring-8 ring-accent-50'
175-
: '',
174+
? 'ring-8 ring-accent-50'
175+
: '',
176176
]
177177
: highlighted,
178178
isSelected && isCTE
179179
? 'ring-8 ring-accent-50'
180180
: isSelected && isModelUnknown
181-
? 'ring-8 ring-neutral-50'
182-
: isSelected && 'ring-8 ring-secondary-50 dark:ring-primary-50',
181+
? 'ring-8 ring-neutral-50'
182+
: isSelected && 'ring-8 ring-secondary-50 dark:ring-primary-50',
183183
)}
184184
style={{
185185
maxWidth: isNil(nodeData.width)

web/client/src/library/components/graph/help.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,15 @@ function getNodeMap({
178178
type: isNotNil(model)
179179
? (model.type as LineageNodeModelType)
180180
: // If model name present in lineage but not in global models
181-
// it means either this is a CTE or model is UNKNOWN
182-
// CTEs only have connections between columns
183-
// where UNKNOWN model has connection only from another model
184-
unknownModels.has(modelName)
185-
? EnumLineageNodeModelType.unknown
186-
: EnumLineageNodeModelType.cte,
181+
// it means either this is a CTE or model is UNKNOWN
182+
// CTEs only have connections between columns
183+
// where UNKNOWN model has connection only from another model
184+
unknownModels.has(modelName)
185+
? EnumLineageNodeModelType.unknown
186+
: EnumLineageNodeModelType.cte,
187187
})
188188
const columnsCount = withColumns
189-
? (models.get(modelName)?.columns?.length ?? 0)
189+
? models.get(modelName)?.columns?.length ?? 0
190190
: 0
191191

192192
const maxWidth = Math.min(

web/client/src/library/components/plan/PlanActions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ export default function PlanActions({
175175
planAction.isRun
176176
? handleRun
177177
: planCancel.isSuccessful
178-
? undefined
179-
: handleApply
178+
? undefined
179+
: handleApply
180180
}
181181
ref={setFocus}
182182
variant={

0 commit comments

Comments
 (0)