sqlnbfmt 0.4.0__tar.gz → 0.4.1__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.
- {sqlnbfmt-0.4.0/sqlnbfmt.egg-info → sqlnbfmt-0.4.1}/PKG-INFO +19 -1
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/README.md +18 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/pyproject.toml +1 -1
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt/formatter.py +89 -92
- sqlnbfmt-0.4.1/sqlnbfmt/jupyterlab_integration.py +74 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1/sqlnbfmt.egg-info}/PKG-INFO +19 -1
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt.egg-info/SOURCES.txt +2 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/tests/test_formatter.py +53 -1
- sqlnbfmt-0.4.1/tests/test_jupyterlab_integration.py +76 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/LICENSE +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/setup.cfg +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt/__init__.py +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt.egg-info/dependency_links.txt +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt.egg-info/entry_points.txt +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt.egg-info/requires.txt +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/sqlnbfmt.egg-info/top_level.txt +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/tests/test_eval.py +0 -0
- {sqlnbfmt-0.4.0 → sqlnbfmt-0.4.1}/tests/test_marimo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlnbfmt
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A tool to format SQL code in Jupyter notebooks and Marimo notebooks.
|
|
5
5
|
Author-email: Qing <qingye779@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -48,6 +48,7 @@ A SQL formatter for Jupyter Notebooks and Marimo notebooks. `sqlnbfmt` automatic
|
|
|
48
48
|
- **CI-Friendly**: `--check` mode exits non-zero when formatting is needed; `--diff` shows what would change
|
|
49
49
|
- **Skip Hints**: Add `# sqlnbfmt: skip` to any cell to exclude it from formatting
|
|
50
50
|
- **Pre-commit Ready**: Seamlessly integrates with pre-commit hooks
|
|
51
|
+
- **JupyterLab Plugin**: Works with `jupyterlab-code-formatter` for interactive format-on-save
|
|
51
52
|
- **Lightweight**: Only three runtime dependencies (sqlglot, nbformat, pyyaml)
|
|
52
53
|
|
|
53
54
|
## Installation
|
|
@@ -94,6 +95,23 @@ Add a `# sqlnbfmt: skip` comment to skip formatting. In Jupyter notebooks this a
|
|
|
94
95
|
query = "select * from my_special_table where id = 1"
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
### JupyterLab Integration
|
|
99
|
+
|
|
100
|
+
Format SQL cells interactively with [jupyterlab-code-formatter](https://github.com/jupyterlab-contrib/jupyterlab_code_formatter):
|
|
101
|
+
|
|
102
|
+
1. Install both packages:
|
|
103
|
+
```bash
|
|
104
|
+
pip install sqlnbfmt jupyterlab-code-formatter
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Register the formatter in `~/.jupyter/jupyter_notebook_config.py`:
|
|
108
|
+
```python
|
|
109
|
+
from sqlnbfmt.jupyterlab_integration import register
|
|
110
|
+
register() # or: register(dialect="postgres")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
3. In JupyterLab, use **Edit > Format Cell** or configure format-on-save in Settings > Code Formatter.
|
|
114
|
+
|
|
97
115
|
### Pre-commit Integration
|
|
98
116
|
|
|
99
117
|
1. Install pre-commit:
|
|
@@ -19,6 +19,7 @@ A SQL formatter for Jupyter Notebooks and Marimo notebooks. `sqlnbfmt` automatic
|
|
|
19
19
|
- **CI-Friendly**: `--check` mode exits non-zero when formatting is needed; `--diff` shows what would change
|
|
20
20
|
- **Skip Hints**: Add `# sqlnbfmt: skip` to any cell to exclude it from formatting
|
|
21
21
|
- **Pre-commit Ready**: Seamlessly integrates with pre-commit hooks
|
|
22
|
+
- **JupyterLab Plugin**: Works with `jupyterlab-code-formatter` for interactive format-on-save
|
|
22
23
|
- **Lightweight**: Only three runtime dependencies (sqlglot, nbformat, pyyaml)
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
@@ -65,6 +66,23 @@ Add a `# sqlnbfmt: skip` comment to skip formatting. In Jupyter notebooks this a
|
|
|
65
66
|
query = "select * from my_special_table where id = 1"
|
|
66
67
|
```
|
|
67
68
|
|
|
69
|
+
### JupyterLab Integration
|
|
70
|
+
|
|
71
|
+
Format SQL cells interactively with [jupyterlab-code-formatter](https://github.com/jupyterlab-contrib/jupyterlab_code_formatter):
|
|
72
|
+
|
|
73
|
+
1. Install both packages:
|
|
74
|
+
```bash
|
|
75
|
+
pip install sqlnbfmt jupyterlab-code-formatter
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
2. Register the formatter in `~/.jupyter/jupyter_notebook_config.py`:
|
|
79
|
+
```python
|
|
80
|
+
from sqlnbfmt.jupyterlab_integration import register
|
|
81
|
+
register() # or: register(dialect="postgres")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
3. In JupyterLab, use **Edit > Format Cell** or configure format-on-save in Settings > Code Formatter.
|
|
85
|
+
|
|
68
86
|
### Pre-commit Integration
|
|
69
87
|
|
|
70
88
|
1. Install pre-commit:
|
|
@@ -457,122 +457,119 @@ def _has_skip_hint(source: str) -> bool:
|
|
|
457
457
|
)
|
|
458
458
|
|
|
459
459
|
|
|
460
|
-
def
|
|
461
|
-
|
|
460
|
+
def format_cell_source(
|
|
461
|
+
code: str,
|
|
462
462
|
config: FormattingConfig,
|
|
463
463
|
dialect: Optional[str],
|
|
464
464
|
logger: logging.Logger,
|
|
465
|
-
) ->
|
|
466
|
-
"""Format
|
|
467
|
-
changed = False
|
|
465
|
+
) -> str:
|
|
466
|
+
"""Format SQL in a single cell's source code.
|
|
468
467
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
468
|
+
Handles both magic commands (%%sql, %sql) and regular Python code
|
|
469
|
+
with embedded SQL strings. Returns the formatted source.
|
|
470
|
+
"""
|
|
471
|
+
if not code.strip():
|
|
472
|
+
return code
|
|
472
473
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
continue
|
|
474
|
+
if _has_skip_hint(code):
|
|
475
|
+
return code
|
|
476
476
|
|
|
477
|
-
|
|
478
|
-
logger.debug(f"[Cell {cell_index}] Skipping cell (sqlnbfmt: skip)")
|
|
479
|
-
continue
|
|
477
|
+
lines = code.split("\n")
|
|
480
478
|
|
|
481
|
-
|
|
479
|
+
# Look for magic commands
|
|
480
|
+
magic_cmd = None
|
|
481
|
+
magic_cmd_index = None
|
|
482
482
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
magic_cmd_index = None
|
|
483
|
+
for idx, line in enumerate(lines):
|
|
484
|
+
stripped = line.strip()
|
|
486
485
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
486
|
+
if not stripped:
|
|
487
|
+
continue
|
|
488
|
+
if stripped.startswith("#"):
|
|
489
|
+
continue
|
|
490
|
+
if stripped.startswith("%%sql") or stripped.startswith("%sql"):
|
|
491
|
+
magic_cmd = stripped.split()[0]
|
|
492
|
+
magic_cmd_index = idx
|
|
493
|
+
break
|
|
494
|
+
else:
|
|
495
|
+
break
|
|
490
496
|
|
|
491
|
-
|
|
492
|
-
|
|
497
|
+
if magic_cmd:
|
|
498
|
+
is_cell_magic = magic_cmd.startswith("%%sql")
|
|
493
499
|
|
|
494
|
-
|
|
495
|
-
|
|
500
|
+
if is_cell_magic:
|
|
501
|
+
sql_code = "\n".join(lines[magic_cmd_index + 1 :]).strip()
|
|
502
|
+
else:
|
|
503
|
+
sql_code = lines[magic_cmd_index][len(magic_cmd) :].strip()
|
|
496
504
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
505
|
+
try:
|
|
506
|
+
formatted_sql = format_sql_code(
|
|
507
|
+
sql_code,
|
|
508
|
+
dialect,
|
|
509
|
+
config,
|
|
510
|
+
is_magic_command=True,
|
|
511
|
+
is_cell_magic=is_cell_magic,
|
|
512
|
+
)
|
|
501
513
|
|
|
514
|
+
if is_cell_magic:
|
|
515
|
+
pre_magic = "\n".join(lines[:magic_cmd_index])
|
|
516
|
+
if pre_magic:
|
|
517
|
+
return f"{pre_magic}\n{magic_cmd}\n{formatted_sql}"
|
|
518
|
+
return f"{magic_cmd}\n{formatted_sql}"
|
|
502
519
|
else:
|
|
503
|
-
|
|
520
|
+
pre_magic = "\n".join(lines[:magic_cmd_index])
|
|
521
|
+
if pre_magic:
|
|
522
|
+
return f"{pre_magic}\n{magic_cmd} {formatted_sql}"
|
|
523
|
+
return f"{magic_cmd} {formatted_sql}"
|
|
504
524
|
|
|
505
|
-
|
|
506
|
-
|
|
525
|
+
except SQLFormattingError as e:
|
|
526
|
+
logger.warning(f"SQL magic formatting skipped: {e}")
|
|
527
|
+
return code
|
|
507
528
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
529
|
+
# Handle regular Python code with surgical string replacement
|
|
530
|
+
try:
|
|
531
|
+
tree = ast.parse(code)
|
|
532
|
+
except SyntaxError:
|
|
533
|
+
logger.warning(f"Failed to parse cell:\n{code}")
|
|
534
|
+
return code
|
|
514
535
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
dialect,
|
|
519
|
-
config,
|
|
520
|
-
is_magic_command=True,
|
|
521
|
-
is_cell_magic=is_cell_magic,
|
|
522
|
-
)
|
|
536
|
+
line_offsets = build_line_offsets(code)
|
|
537
|
+
formatter = SQLStringFormatter(config, dialect, logger, line_offsets)
|
|
538
|
+
formatter.visit(tree)
|
|
523
539
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
# Preserve comments before the magic command
|
|
527
|
-
pre_magic = "\n".join(lines[:magic_cmd_index])
|
|
528
|
-
if pre_magic:
|
|
529
|
-
new_content = f"{pre_magic}\n{magic_cmd}\n{formatted_sql}"
|
|
530
|
-
else:
|
|
531
|
-
new_content = f"{magic_cmd}\n{formatted_sql}"
|
|
532
|
-
else:
|
|
533
|
-
# Preserve comments before the magic command
|
|
534
|
-
pre_magic = "\n".join(lines[:magic_cmd_index])
|
|
535
|
-
if pre_magic:
|
|
536
|
-
new_content = f"{pre_magic}\n{magic_cmd} {formatted_sql}"
|
|
537
|
-
else:
|
|
538
|
-
new_content = f"{magic_cmd} {formatted_sql}"
|
|
540
|
+
if not formatter.replacements:
|
|
541
|
+
return code
|
|
539
542
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
+
result = code
|
|
544
|
+
for rep in sorted(
|
|
545
|
+
formatter.replacements,
|
|
546
|
+
key=lambda r: r.start_offset,
|
|
547
|
+
reverse=True,
|
|
548
|
+
):
|
|
549
|
+
result = result[: rep.start_offset] + rep.new_text + result[rep.end_offset :]
|
|
543
550
|
|
|
544
|
-
|
|
545
|
-
logger.warning(f"[Cell {cell_index}] SQL magic formatting skipped: {e}")
|
|
551
|
+
return result
|
|
546
552
|
|
|
547
|
-
continue # Move to the next cell after handling magic command
|
|
548
553
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
reverse=True,
|
|
561
|
-
):
|
|
562
|
-
result = (
|
|
563
|
-
result[: rep.start_offset]
|
|
564
|
-
+ rep.new_text
|
|
565
|
-
+ result[rep.end_offset :]
|
|
566
|
-
)
|
|
567
|
-
if result != original_code:
|
|
568
|
-
cell.source = result
|
|
569
|
-
changed = True
|
|
570
|
-
except SyntaxError:
|
|
571
|
-
logger.warning(
|
|
572
|
-
f"[Cell {cell_index}] Failed to parse cell:\n{original_code}"
|
|
573
|
-
)
|
|
554
|
+
def _format_cells(
|
|
555
|
+
notebook: nbformat.NotebookNode,
|
|
556
|
+
config: FormattingConfig,
|
|
557
|
+
dialect: Optional[str],
|
|
558
|
+
logger: logging.Logger,
|
|
559
|
+
) -> bool:
|
|
560
|
+
"""Format cells in-place. Returns True if any cell was modified."""
|
|
561
|
+
changed = False
|
|
562
|
+
|
|
563
|
+
for cell in notebook.cells:
|
|
564
|
+
if cell.cell_type != "code":
|
|
574
565
|
continue
|
|
575
566
|
|
|
567
|
+
original_code = cell.source
|
|
568
|
+
formatted = format_cell_source(original_code, config, dialect, logger)
|
|
569
|
+
if formatted != original_code:
|
|
570
|
+
cell.source = formatted
|
|
571
|
+
changed = True
|
|
572
|
+
|
|
576
573
|
return changed
|
|
577
574
|
|
|
578
575
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""JupyterLab code formatter integration.
|
|
2
|
+
|
|
3
|
+
Provides a formatter plugin for jupyterlab-code-formatter that formats
|
|
4
|
+
SQL in notebook cells (both magic commands and Python strings).
|
|
5
|
+
|
|
6
|
+
Setup: add this to your ``jupyter_notebook_config.py``::
|
|
7
|
+
|
|
8
|
+
from sqlnbfmt.jupyterlab_integration import register
|
|
9
|
+
register()
|
|
10
|
+
|
|
11
|
+
Then select "Apply SQL Notebook Formatter" in JupyterLab's
|
|
12
|
+
Code > Format Cell menu or configure it for format-on-save.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_formatter_class():
|
|
22
|
+
"""Build the formatter class lazily to avoid hard dependency."""
|
|
23
|
+
from jupyterlab_code_formatter.formatters import BaseFormatter
|
|
24
|
+
|
|
25
|
+
from sqlnbfmt.formatter import FormattingConfig, format_cell_source, load_config
|
|
26
|
+
|
|
27
|
+
class SQLNotebookFormatter(BaseFormatter):
|
|
28
|
+
label = "Apply SQL Notebook Formatter"
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
config: Optional[FormattingConfig] = None,
|
|
33
|
+
dialect: Optional[str] = None,
|
|
34
|
+
):
|
|
35
|
+
self._config = config or load_config()
|
|
36
|
+
self._dialect = dialect
|
|
37
|
+
self._logger = logging.getLogger("sqlnbfmt.jupyterlab")
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def importable(self) -> bool:
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
def format_code(self, code: str, notebook: bool, **options) -> str:
|
|
44
|
+
dialect = options.pop("dialect", self._dialect)
|
|
45
|
+
return format_cell_source(code, self._config, dialect, self._logger)
|
|
46
|
+
|
|
47
|
+
return SQLNotebookFormatter
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def register(
|
|
51
|
+
name: str = "sqlnbfmt",
|
|
52
|
+
dialect: Optional[str] = None,
|
|
53
|
+
config_path: Optional[str] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Register sqlnbfmt with jupyterlab-code-formatter.
|
|
56
|
+
|
|
57
|
+
Call this in ``jupyter_notebook_config.py``::
|
|
58
|
+
|
|
59
|
+
from sqlnbfmt.jupyterlab_integration import register
|
|
60
|
+
register() # defaults
|
|
61
|
+
register(dialect="postgres") # with options
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name: Key used in JupyterLab settings to reference this formatter.
|
|
65
|
+
dialect: SQL dialect (e.g. ``"postgres"``, ``"mysql"``).
|
|
66
|
+
config_path: Path to a sqlnbfmt YAML config file.
|
|
67
|
+
"""
|
|
68
|
+
from jupyterlab_code_formatter.formatters import SERVER_FORMATTERS
|
|
69
|
+
|
|
70
|
+
from sqlnbfmt.formatter import load_config
|
|
71
|
+
|
|
72
|
+
config = load_config(config_path)
|
|
73
|
+
cls = _get_formatter_class()
|
|
74
|
+
SERVER_FORMATTERS[name] = cls(config=config, dialect=dialect)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlnbfmt
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A tool to format SQL code in Jupyter notebooks and Marimo notebooks.
|
|
5
5
|
Author-email: Qing <qingye779@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -48,6 +48,7 @@ A SQL formatter for Jupyter Notebooks and Marimo notebooks. `sqlnbfmt` automatic
|
|
|
48
48
|
- **CI-Friendly**: `--check` mode exits non-zero when formatting is needed; `--diff` shows what would change
|
|
49
49
|
- **Skip Hints**: Add `# sqlnbfmt: skip` to any cell to exclude it from formatting
|
|
50
50
|
- **Pre-commit Ready**: Seamlessly integrates with pre-commit hooks
|
|
51
|
+
- **JupyterLab Plugin**: Works with `jupyterlab-code-formatter` for interactive format-on-save
|
|
51
52
|
- **Lightweight**: Only three runtime dependencies (sqlglot, nbformat, pyyaml)
|
|
52
53
|
|
|
53
54
|
## Installation
|
|
@@ -94,6 +95,23 @@ Add a `# sqlnbfmt: skip` comment to skip formatting. In Jupyter notebooks this a
|
|
|
94
95
|
query = "select * from my_special_table where id = 1"
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
### JupyterLab Integration
|
|
99
|
+
|
|
100
|
+
Format SQL cells interactively with [jupyterlab-code-formatter](https://github.com/jupyterlab-contrib/jupyterlab_code_formatter):
|
|
101
|
+
|
|
102
|
+
1. Install both packages:
|
|
103
|
+
```bash
|
|
104
|
+
pip install sqlnbfmt jupyterlab-code-formatter
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Register the formatter in `~/.jupyter/jupyter_notebook_config.py`:
|
|
108
|
+
```python
|
|
109
|
+
from sqlnbfmt.jupyterlab_integration import register
|
|
110
|
+
register() # or: register(dialect="postgres")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
3. In JupyterLab, use **Edit > Format Cell** or configure format-on-save in Settings > Code Formatter.
|
|
114
|
+
|
|
97
115
|
### Pre-commit Integration
|
|
98
116
|
|
|
99
117
|
1. Install pre-commit:
|
|
@@ -3,6 +3,7 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
sqlnbfmt/__init__.py
|
|
5
5
|
sqlnbfmt/formatter.py
|
|
6
|
+
sqlnbfmt/jupyterlab_integration.py
|
|
6
7
|
sqlnbfmt.egg-info/PKG-INFO
|
|
7
8
|
sqlnbfmt.egg-info/SOURCES.txt
|
|
8
9
|
sqlnbfmt.egg-info/dependency_links.txt
|
|
@@ -11,4 +12,5 @@ sqlnbfmt.egg-info/requires.txt
|
|
|
11
12
|
sqlnbfmt.egg-info/top_level.txt
|
|
12
13
|
tests/test_eval.py
|
|
13
14
|
tests/test_formatter.py
|
|
15
|
+
tests/test_jupyterlab_integration.py
|
|
14
16
|
tests/test_marimo.py
|
|
@@ -3,7 +3,12 @@ import nbformat
|
|
|
3
3
|
from nbformat import v4 as nbf
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
-
from sqlnbfmt.formatter import
|
|
6
|
+
from sqlnbfmt.formatter import (
|
|
7
|
+
format_cell_source,
|
|
8
|
+
process_notebook,
|
|
9
|
+
load_config,
|
|
10
|
+
diff_notebook,
|
|
11
|
+
)
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
# Fixture for temporary notebook path
|
|
@@ -461,3 +466,50 @@ def test_main_check_diff_exits_one(tmp_path):
|
|
|
461
466
|
)
|
|
462
467
|
assert result.returncode == 1
|
|
463
468
|
assert "---" in result.stdout # diff was printed
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# ---------------------------------------------------------------------------
|
|
472
|
+
# format_cell_source tests
|
|
473
|
+
# ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def test_format_cell_source_magic(logger):
|
|
477
|
+
"""format_cell_source handles %%sql magic cells."""
|
|
478
|
+
config = load_config()
|
|
479
|
+
code = "%%sql\nselect id, name from users where active = 1"
|
|
480
|
+
result = format_cell_source(code, config, "mysql", logger)
|
|
481
|
+
assert result.startswith("%%sql\n")
|
|
482
|
+
assert "SELECT" in result
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def test_format_cell_source_line_magic(logger):
|
|
486
|
+
"""format_cell_source handles %sql line magic."""
|
|
487
|
+
config = load_config()
|
|
488
|
+
code = "%sql select * from users where active = 1"
|
|
489
|
+
result = format_cell_source(code, config, "mysql", logger)
|
|
490
|
+
assert result.startswith("%sql ")
|
|
491
|
+
assert "SELECT" in result
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def test_format_cell_source_python(logger):
|
|
495
|
+
"""format_cell_source handles regular Python code with SQL."""
|
|
496
|
+
config = load_config()
|
|
497
|
+
code = 'query = "select id, name from users where active = 1"'
|
|
498
|
+
result = format_cell_source(code, config, "mysql", logger)
|
|
499
|
+
assert "SELECT" in result
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def test_format_cell_source_non_sql(logger):
|
|
503
|
+
"""format_cell_source returns non-SQL code unchanged."""
|
|
504
|
+
config = load_config()
|
|
505
|
+
code = "x = 42\nprint(x)"
|
|
506
|
+
result = format_cell_source(code, config, "mysql", logger)
|
|
507
|
+
assert result == code
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def test_format_cell_source_skip_hint(logger):
|
|
511
|
+
"""format_cell_source respects skip hints."""
|
|
512
|
+
config = load_config()
|
|
513
|
+
code = '# sqlnbfmt: skip\nquery = "select * from users"'
|
|
514
|
+
result = format_cell_source(code, config, "mysql", logger)
|
|
515
|
+
assert result == code
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Tests for the JupyterLab code formatter integration.
|
|
2
|
+
|
|
3
|
+
Since jupyterlab-code-formatter is not a dependency, these tests
|
|
4
|
+
mock the necessary imports and verify the adapter works correctly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_register_adds_to_server_formatters():
|
|
11
|
+
"""register() inserts an instance into SERVER_FORMATTERS."""
|
|
12
|
+
mock_formatters = {}
|
|
13
|
+
mock_base = type("BaseFormatter", (), {})
|
|
14
|
+
|
|
15
|
+
with (
|
|
16
|
+
patch.dict(
|
|
17
|
+
"sys.modules",
|
|
18
|
+
{
|
|
19
|
+
"jupyterlab_code_formatter": MagicMock(),
|
|
20
|
+
"jupyterlab_code_formatter.formatters": MagicMock(
|
|
21
|
+
BaseFormatter=mock_base,
|
|
22
|
+
SERVER_FORMATTERS=mock_formatters,
|
|
23
|
+
),
|
|
24
|
+
},
|
|
25
|
+
),
|
|
26
|
+
):
|
|
27
|
+
from sqlnbfmt.jupyterlab_integration import register
|
|
28
|
+
|
|
29
|
+
register()
|
|
30
|
+
assert "sqlnbfmt" in mock_formatters
|
|
31
|
+
|
|
32
|
+
register(name="custom_name", dialect="postgres")
|
|
33
|
+
assert "custom_name" in mock_formatters
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_formatter_class_format_code():
|
|
37
|
+
"""The formatter class delegates to format_cell_source."""
|
|
38
|
+
mock_base = type("BaseFormatter", (), {})
|
|
39
|
+
|
|
40
|
+
with patch.dict(
|
|
41
|
+
"sys.modules",
|
|
42
|
+
{
|
|
43
|
+
"jupyterlab_code_formatter": MagicMock(),
|
|
44
|
+
"jupyterlab_code_formatter.formatters": MagicMock(
|
|
45
|
+
BaseFormatter=mock_base,
|
|
46
|
+
SERVER_FORMATTERS={},
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
):
|
|
50
|
+
from sqlnbfmt.jupyterlab_integration import _get_formatter_class
|
|
51
|
+
|
|
52
|
+
cls = _get_formatter_class()
|
|
53
|
+
formatter = cls()
|
|
54
|
+
|
|
55
|
+
assert formatter.label == "Apply SQL Notebook Formatter"
|
|
56
|
+
assert formatter.importable is True
|
|
57
|
+
|
|
58
|
+
# Format a cell with SQL
|
|
59
|
+
result = formatter.format_code(
|
|
60
|
+
'query = "select id, name from users where active = 1"',
|
|
61
|
+
notebook=True,
|
|
62
|
+
)
|
|
63
|
+
assert "SELECT" in result
|
|
64
|
+
|
|
65
|
+
# Format a %%sql magic cell
|
|
66
|
+
result = formatter.format_code(
|
|
67
|
+
"%%sql\nselect * from users where active = 1",
|
|
68
|
+
notebook=True,
|
|
69
|
+
)
|
|
70
|
+
assert "%%sql" in result
|
|
71
|
+
assert "SELECT" in result
|
|
72
|
+
|
|
73
|
+
# Non-SQL code passes through unchanged
|
|
74
|
+
code = "x = 42\nprint(x)"
|
|
75
|
+
result = formatter.format_code(code, notebook=True)
|
|
76
|
+
assert result == code
|
|
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
|