Skip to content

Commit d8b8836

Browse files
authored
Add IDE to CLI and package (#636)
* Add IDE to CLI and package * Update CI * Update Pyodide workers * Change prod to dist * PR feedback
1 parent 722c745 commit d8b8836

10 files changed

Lines changed: 134 additions & 13 deletions

File tree

.circleci/config.yml

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
publish:
1717
docker:
1818
- image: cimg/python:3.10
19+
resource_class: small
1920
steps:
2021
- checkout
2122
- run:
@@ -25,6 +26,7 @@ jobs:
2526
gh-release:
2627
docker:
2728
- image: cimg/node:16.14
29+
resource_class: small
2830
steps:
2931
- run:
3032
name: Create release on GitHub
@@ -36,6 +38,42 @@ jobs:
3638
CONTINUE_ON_ERROR="false" \
3739
npx https://github.com/TobikoData/circleci-gh-conventional-release
3840
41+
generate-openapi-spec:
42+
docker:
43+
- image: cimg/python:3.11
44+
resource_class: small
45+
steps:
46+
- checkout
47+
- run:
48+
name: Install dependencies
49+
command: make install-dev
50+
- run:
51+
name: Generate OpenAPI spec
52+
command: python web/server/openapi.py
53+
- persist_to_workspace:
54+
root: web/client
55+
paths:
56+
- openapi.json
57+
58+
ide-build:
59+
docker:
60+
- image: cimg/node:19.8
61+
resource_class: small
62+
steps:
63+
- checkout
64+
- attach_workspace:
65+
at: web/client
66+
- run:
67+
name: Install packages
68+
command: npm --prefix web/client ci
69+
- run:
70+
name: Build IDE
71+
command: npm --prefix web/client run build
72+
- persist_to_workspace:
73+
root: web/client
74+
paths:
75+
- dist
76+
3977
workflows:
4078
setup-workflow:
4179
jobs:
@@ -50,7 +88,12 @@ workflows:
5088
jobs:
5189
- gh-release:
5290
<<: *on_tag_filter
53-
- publish:
54-
<<: *on_tag_filter
91+
- generate-openapi-spec:
5592
requires:
5693
- gh-release
94+
- ide-build:
95+
requires:
96+
- generate-openapi-spec
97+
- publish:
98+
requires:
99+
- ide-build

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ examples/airflow/warehouse
149149
**/.DS_Store
150150
**/node_modules
151151
/web/client/src/api/client.ts
152+
web/client/dist
152153

153154
# Version file
154155
sqlmesh/_version.py
156+
157+
# Autogenerated OpenAPI spec
158+
openapi.json

docker-compose.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
version: "3.11"
22
services:
3+
generate-openapi-spec:
4+
image: tobiko-api
5+
container_name: generate-openapi-spec
6+
working_dir: /sqlmesh
7+
build:
8+
dockerfile: Dockerfile.api
9+
command: python web/server/openapi.py
10+
volumes:
11+
- .:/sqlmesh
12+
313
app:
414
image: tobiko-app
515
container_name: tobiko-app
@@ -16,7 +26,7 @@ services:
1626
networks:
1727
- tobiko-development
1828
depends_on:
19-
- api
29+
- generate-openapi-spec
2030

2131
api:
2232
image: tobiko-api

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
author="TobikoData Inc.",
1515
author_email="engineering@tobikodata.com",
1616
license="Apache License 2.0",
17-
packages=find_packages(include=["sqlmesh", "sqlmesh.*"]),
17+
packages=find_packages(include=["sqlmesh", "sqlmesh.*", "web*"]),
18+
package_data={"web": ["client/dist/**"]},
1819
entry_points={
1920
"console_scripts": [
2021
"sqlmesh = sqlmesh.cli.main:cli",

sqlmesh/cli/main.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sqlmesh.cli.example_project import ProjectTemplate, init_example_project
1212
from sqlmesh.core.context import Context
1313
from sqlmesh.utils.date import TimeLike
14+
from sqlmesh.utils.errors import MissingDependencyError
1415

1516

1617
@click.group(no_args_is_help=True)
@@ -308,5 +309,37 @@ def version() -> None:
308309
print("Version is not available")
309310

310311

312+
@cli.command("ide")
313+
@click.option(
314+
"--host",
315+
type=str,
316+
help="Bind socket to this host. Default: 127.0.0.1",
317+
)
318+
@click.option(
319+
"--port",
320+
type=int,
321+
help="Bind socket to this port. Default: 8000",
322+
)
323+
@click.pass_obj
324+
@error_handler
325+
def ide(
326+
obj: Context,
327+
host: t.Optional[str],
328+
port: t.Optional[int],
329+
) -> None:
330+
"""Start a browser-based SQLMesh IDE."""
331+
try:
332+
import uvicorn
333+
except ModuleNotFoundError as e:
334+
raise MissingDependencyError(
335+
"Missing IDE dependencies. Run `pip install sqlmesh[web]` to install them."
336+
) from e
337+
338+
host = host or "127.0.0.1"
339+
port = 8000 if port is None else port
340+
os.environ["PROJECT_PATH"] = str(obj.path)
341+
uvicorn.run("web.server.main:app", host=host, port=port, log_level="info")
342+
343+
311344
if __name__ == "__main__":
312345
cli()

web/client/orval.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineConfig } from 'orval'
22

33
export default defineConfig({
44
'sqlmesh-api': {
5-
input: 'http://api:8000/openapi.json',
5+
input: './openapi.json',
66
output: {
77
prettier: true,
88
target: './src/api/client.ts',

web/client/src/workers/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
const sqlglotWorker = new Worker(
22
new URL('./sqlglot/worker.ts', import.meta.url),
3-
{
4-
type: 'module',
5-
},
63
)
74

85
export { sqlglotWorker }

web/client/src/workers/sqlglot/worker.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js'
2-
3-
export {}
4-
51
const global = self as any
2+
global.importScripts('https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js')
63

74
async function loadPyodideAndPackages(): Promise<any[]> {
85
global.pyodide = await global.loadPyodide()
96
await global.pyodide.loadPackage('micropip')
107

118
const micropip = global.pyodide.pyimport('micropip')
129
await micropip.install('sqlglot')
13-
const file = await (await fetch('./sqlglot.py')).text()
10+
const file = await (
11+
await fetch(new URL('./sqlglot.py', import.meta.url))
12+
).text()
1413

1514
global.postMessage({ topic: 'init' })
1615

web/server/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import asyncio
2+
import pathlib
23

34
from fastapi import FastAPI
5+
from fastapi.responses import HTMLResponse
6+
from fastapi.staticfiles import StaticFiles
47

58
from web.server.api.endpoints import api_router
69
from web.server.console import ApiConsole
@@ -9,6 +12,7 @@
912
api_console = ApiConsole()
1013

1114
app.include_router(api_router, prefix="/api")
15+
WEB_DIRECTORY = pathlib.Path(__file__).parent.parent
1216

1317

1418
@app.on_event("startup")
@@ -32,3 +36,21 @@ def shutdown_event() -> None:
3236
@app.get("/health")
3337
def health() -> str:
3438
return "ok"
39+
40+
41+
@app.get("/")
42+
def index() -> HTMLResponse:
43+
with open(WEB_DIRECTORY / "client/dist/index.html", "r", encoding="utf-8") as f:
44+
return HTMLResponse(f.read())
45+
46+
47+
app.mount(
48+
"/assets",
49+
StaticFiles(directory=WEB_DIRECTORY / "client/dist/assets", check_dir=False),
50+
name="assets",
51+
)
52+
app.mount(
53+
"/favicons",
54+
StaticFiles(directory=WEB_DIRECTORY / "client/dist/favicons", check_dir=False),
55+
name="favicons",
56+
)

web/server/openapi.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import json
2+
3+
from web.server.main import app
4+
5+
6+
def generate_openapi_spec(path: str) -> None:
7+
with open(path, "w", encoding="utf-8") as f:
8+
json.dump(app.openapi(), f)
9+
10+
11+
if __name__ == "__main__":
12+
generate_openapi_spec("web/client/openapi.json")

0 commit comments

Comments
 (0)