quickdbd-cli 0.1.9__tar.gz → 0.1.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/PKG-INFO +4 -4
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/README.md +3 -3
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/pyproject.toml +1 -1
- quickdbd_cli-0.1.10/src/quickdbd_cli/__init__.py +1 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/cli.py +4 -2
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/serve.py +30 -5
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_serve.py +33 -0
- quickdbd_cli-0.1.9/src/quickdbd_cli/__init__.py +0 -1
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/.gitignore +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/LICENSE +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/NOTICE +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/quickdbd-sample.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/synthetic/finance.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/synthetic/hr.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/synthetic/inventory.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/synthetic/marketing.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/synthetic/qdbd.layout.json +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/examples/synthetic/sales.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/scripts/gen_font_metrics.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/scripts/gen_schema.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/scripts/release.sh +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/__main__.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/editor.js +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/font-metrics.json +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/render.js +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/style.css +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/assets/viewer.js +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/dbt.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/diff.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/export.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/html.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/jsrt.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/layout.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/measure.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/model.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/parser.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/project.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/share.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/src/quickdbd_cli/tags.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/fixtures/jaffle/LICENSE +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/fixtures/jaffle/README.md +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/fixtures/jaffle/catalog.json +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/fixtures/jaffle/manifest.json +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_dbt.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_diff.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_parser.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_path.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_pipeline.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_select.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_share.py +0 -0
- {quickdbd_cli-0.1.9 → quickdbd_cli-0.1.10}/tests/test_tags.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quickdbd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
|
|
5
5
|
Project-URL: Homepage, https://github.com/MarkPhamm/quickdbd-cli
|
|
6
6
|
Author: Minh (Mark) Pham
|
|
@@ -387,7 +387,7 @@ jobs:
|
|
|
387
387
|
permissions: { contents: read, pull-requests: write }
|
|
388
388
|
steps:
|
|
389
389
|
- uses: actions/checkout@v4
|
|
390
|
-
- uses: MarkPhamm/quickdbd-cli@v0.1.
|
|
390
|
+
- uses: MarkPhamm/quickdbd-cli@v0.1.10
|
|
391
391
|
with:
|
|
392
392
|
path: erd/ # schema file(s) or folder(s)
|
|
393
393
|
output: site/index.html
|
|
@@ -410,7 +410,7 @@ Run your dbt setup first, then let the action generate the schema:
|
|
|
410
410
|
|
|
411
411
|
```yaml
|
|
412
412
|
# … dbt deps && dbt build && dbt docs generate
|
|
413
|
-
- uses: MarkPhamm/quickdbd-cli@v0.1.
|
|
413
|
+
- uses: MarkPhamm/quickdbd-cli@v0.1.10
|
|
414
414
|
with:
|
|
415
415
|
dbt-manifest: target/
|
|
416
416
|
dbt-args: --infer-fks
|
|
@@ -425,7 +425,7 @@ Commit `erd/` so pull requests can be compared with `main`. Set `dbt-check: true
|
|
|
425
425
|
```yaml
|
|
426
426
|
repos:
|
|
427
427
|
- repo: https://github.com/MarkPhamm/quickdbd-cli
|
|
428
|
-
rev: v0.1.
|
|
428
|
+
rev: v0.1.10
|
|
429
429
|
hooks:
|
|
430
430
|
- id: qdbd-check
|
|
431
431
|
files: ^erd/.*\.txt$
|
|
@@ -359,7 +359,7 @@ jobs:
|
|
|
359
359
|
permissions: { contents: read, pull-requests: write }
|
|
360
360
|
steps:
|
|
361
361
|
- uses: actions/checkout@v4
|
|
362
|
-
- uses: MarkPhamm/quickdbd-cli@v0.1.
|
|
362
|
+
- uses: MarkPhamm/quickdbd-cli@v0.1.10
|
|
363
363
|
with:
|
|
364
364
|
path: erd/ # schema file(s) or folder(s)
|
|
365
365
|
output: site/index.html
|
|
@@ -382,7 +382,7 @@ Run your dbt setup first, then let the action generate the schema:
|
|
|
382
382
|
|
|
383
383
|
```yaml
|
|
384
384
|
# … dbt deps && dbt build && dbt docs generate
|
|
385
|
-
- uses: MarkPhamm/quickdbd-cli@v0.1.
|
|
385
|
+
- uses: MarkPhamm/quickdbd-cli@v0.1.10
|
|
386
386
|
with:
|
|
387
387
|
dbt-manifest: target/
|
|
388
388
|
dbt-args: --infer-fks
|
|
@@ -397,7 +397,7 @@ Commit `erd/` so pull requests can be compared with `main`. Set `dbt-check: true
|
|
|
397
397
|
```yaml
|
|
398
398
|
repos:
|
|
399
399
|
- repo: https://github.com/MarkPhamm/quickdbd-cli
|
|
400
|
-
rev: v0.1.
|
|
400
|
+
rev: v0.1.10
|
|
401
401
|
hooks:
|
|
402
402
|
- id: qdbd-check
|
|
403
403
|
files: ^erd/.*\.txt$
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quickdbd-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.10"
|
|
8
8
|
description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.10"
|
|
@@ -318,7 +318,9 @@ def cmd_serve(args) -> int:
|
|
|
318
318
|
from .serve import serve
|
|
319
319
|
|
|
320
320
|
project = _open(args)
|
|
321
|
-
|
|
321
|
+
# Without --port, a busy 8765 (e.g. a second `qdbd serve`) moves on to the next free port.
|
|
322
|
+
port, tries = (args.port, 1) if args.port is not None else (8765, 20)
|
|
323
|
+
return serve(project, port=port, open_browser=not args.no_open, read_only=args.read_only, tries=tries)
|
|
322
324
|
|
|
323
325
|
|
|
324
326
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -437,7 +439,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
437
439
|
|
|
438
440
|
sp = sub.add_parser("serve", help="live editor: reload on save, drag tables to arrange")
|
|
439
441
|
paths(sp)
|
|
440
|
-
sp.add_argument("--port", type=int, default
|
|
442
|
+
sp.add_argument("--port", type=int, help="port to listen on (default: 8765, or the next free one)")
|
|
441
443
|
sp.add_argument("--layout")
|
|
442
444
|
sp.add_argument("--no-open", action="store_true", help="don't open a browser")
|
|
443
445
|
sp.add_argument(
|
|
@@ -9,6 +9,7 @@ the server never writes anything.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import errno
|
|
12
13
|
import json
|
|
13
14
|
import sys
|
|
14
15
|
import threading
|
|
@@ -190,11 +191,29 @@ def _handler(state: State, title: str):
|
|
|
190
191
|
return Handler
|
|
191
192
|
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
class PortInUse(OSError):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def make_server(
|
|
199
|
+
project: Project, port: int = 8765, read_only: bool = False, tries: int = 1
|
|
200
|
+
) -> tuple[ThreadingHTTPServer, State]:
|
|
201
|
+
"""Bind the HTTP server without starting it. Port 0 picks a free port; with ``tries`` > 1, a busy port
|
|
202
|
+
moves on to the next one (port, port+1, ...)."""
|
|
195
203
|
state = State(project, read_only=read_only)
|
|
196
204
|
title = project.inputs[0].stem if len(project.inputs) == 1 else "diagram"
|
|
197
|
-
server =
|
|
205
|
+
server = None
|
|
206
|
+
for p in range(port, port + max(tries, 1)) if port else [0]:
|
|
207
|
+
try:
|
|
208
|
+
server = ThreadingHTTPServer(("127.0.0.1", p), _handler(state, title))
|
|
209
|
+
break
|
|
210
|
+
except OSError as e:
|
|
211
|
+
if e.errno != errno.EADDRINUSE:
|
|
212
|
+
raise
|
|
213
|
+
if server is None:
|
|
214
|
+
last = port + max(tries, 1) - 1
|
|
215
|
+
span = f"ports {port}-{last} are" if last > port else f"port {port} is"
|
|
216
|
+
raise PortInUse(f"{span} already in use (another qdbd serve?); pick one with --port")
|
|
198
217
|
server.daemon_threads = True
|
|
199
218
|
return server, state
|
|
200
219
|
|
|
@@ -210,8 +229,14 @@ def poll_once(state: State) -> bool:
|
|
|
210
229
|
return True
|
|
211
230
|
|
|
212
231
|
|
|
213
|
-
def serve(
|
|
214
|
-
|
|
232
|
+
def serve(
|
|
233
|
+
project: Project, port: int = 8765, open_browser: bool = True, read_only: bool = False, tries: int = 1
|
|
234
|
+
) -> int:
|
|
235
|
+
try:
|
|
236
|
+
server, state = make_server(project, port, read_only=read_only, tries=tries)
|
|
237
|
+
except PortInUse as e:
|
|
238
|
+
print(f"error: {e}", file=sys.stderr)
|
|
239
|
+
return 2
|
|
215
240
|
for d in state.diagnostics:
|
|
216
241
|
print(d["text"], file=sys.stderr)
|
|
217
242
|
url = f"http://127.0.0.1:{server.server_address[1]}/"
|
|
@@ -194,3 +194,36 @@ def test_read_only_refuses_writes_and_never_touches_disk(ro):
|
|
|
194
194
|
assert poll_once(state)
|
|
195
195
|
assert [t["name"] for t in call(base, "/state")[1]["model"]["tables"]] == ["Customer", "Order", "Product"]
|
|
196
196
|
assert not f.with_name("shop.layout.json").exists()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------------ busy ports
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_busy_port_moves_to_the_next_free_one(tmp_path):
|
|
203
|
+
from quickdbd_cli.serve import PortInUse
|
|
204
|
+
|
|
205
|
+
f = tmp_path / "shop.txt"
|
|
206
|
+
f.write_text(SCHEMA)
|
|
207
|
+
first, _ = make_server(Project.open([f]), port=0)
|
|
208
|
+
busy = first.server_address[1]
|
|
209
|
+
try:
|
|
210
|
+
with pytest.raises(PortInUse, match=f"port {busy} is already in use"):
|
|
211
|
+
make_server(Project.open([f]), port=busy)
|
|
212
|
+
second, _ = make_server(Project.open([f]), port=busy, tries=20)
|
|
213
|
+
assert second.server_address[1] > busy
|
|
214
|
+
second.server_close()
|
|
215
|
+
finally:
|
|
216
|
+
first.server_close()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_cli_explicit_busy_port_is_a_clean_error(tmp_path, capsys):
|
|
220
|
+
from quickdbd_cli.cli import main
|
|
221
|
+
|
|
222
|
+
f = tmp_path / "shop.txt"
|
|
223
|
+
f.write_text(SCHEMA)
|
|
224
|
+
first, _ = make_server(Project.open([f]), port=0)
|
|
225
|
+
try:
|
|
226
|
+
assert main(["serve", str(f), "--no-open", "--port", str(first.server_address[1])]) == 2
|
|
227
|
+
assert "already in use" in capsys.readouterr().err
|
|
228
|
+
finally:
|
|
229
|
+
first.server_close()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|