sqlnbfmt 0.1.2__tar.gz → 0.1.4__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.1.2/sqlnbfmt.egg-info → sqlnbfmt-0.1.4}/PKG-INFO +3 -1
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/pyproject.toml +9 -2
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt/formatter.py +63 -38
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4/sqlnbfmt.egg-info}/PKG-INFO +3 -1
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/tests/test_formatter.py +22 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/LICENSE +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/README.md +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/setup.cfg +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt/__init__.py +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt.egg-info/SOURCES.txt +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt.egg-info/dependency_links.txt +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt.egg-info/entry_points.txt +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt.egg-info/requires.txt +0 -0
- {sqlnbfmt-0.1.2 → sqlnbfmt-0.1.4}/sqlnbfmt.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sqlnbfmt
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A tool to format SQL code in Jupyter notebooks.
|
|
5
5
|
Author-email: Qing <qingye779@gmail.com>
|
|
6
6
|
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
7
9
|
Requires-Python: >=3.12
|
|
8
10
|
Description-Content-Type: text/markdown
|
|
9
11
|
License-File: LICENSE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlnbfmt"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "A tool to format SQL code in Jupyter notebooks."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Qing", email = "qingye779@gmail.com" }
|
|
@@ -8,6 +8,10 @@ authors = [
|
|
|
8
8
|
license = { text = "MIT" }
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.12"
|
|
14
|
+
]
|
|
11
15
|
dependencies = [
|
|
12
16
|
"astor>=0.8.1",
|
|
13
17
|
"nbformat>=5.10.4",
|
|
@@ -31,4 +35,7 @@ sqlnbfmt = "sqlnbfmt.formatter:main"
|
|
|
31
35
|
[tool.pytest.ini_options]
|
|
32
36
|
markers = [
|
|
33
37
|
"integration: mark a test as an integration test."
|
|
34
|
-
]
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
sqlnbfmt = [".pre-commit-hooks.yaml"]
|
|
@@ -206,6 +206,10 @@ class SQLStringFormatter(ast.NodeTransformer):
|
|
|
206
206
|
placeholders = {}
|
|
207
207
|
placeholder_counter = 0
|
|
208
208
|
|
|
209
|
+
# Handle empty f-strings or f-strings with only constants
|
|
210
|
+
if all(isinstance(value, ast.Constant) for value in node.values):
|
|
211
|
+
return "".join(value.value for value in node.values), {}
|
|
212
|
+
|
|
209
213
|
for value in node.values:
|
|
210
214
|
if isinstance(value, ast.Constant):
|
|
211
215
|
sql_parts.append(value.value)
|
|
@@ -236,10 +240,8 @@ class SQLStringFormatter(ast.NodeTransformer):
|
|
|
236
240
|
self.changed = True
|
|
237
241
|
# Reconstruct the string with adjusted triple quotes
|
|
238
242
|
if '\n' in formatted_sql:
|
|
239
|
-
# Multi-line string: place triple quotes on their own lines
|
|
240
243
|
formatted_str = f'"""\n{formatted_sql}\n"""'
|
|
241
244
|
else:
|
|
242
|
-
# Single-line string
|
|
243
245
|
formatted_str = f'"""{formatted_sql}"""'
|
|
244
246
|
# Parse the formatted string back into an AST node
|
|
245
247
|
formatted_node = ast.parse(formatted_str).body[0].value
|
|
@@ -247,19 +249,24 @@ class SQLStringFormatter(ast.NodeTransformer):
|
|
|
247
249
|
|
|
248
250
|
elif isinstance(node, ast.JoinedStr):
|
|
249
251
|
sql_str, placeholders = self.extract_fstring_parts(node)
|
|
250
|
-
if not self.is_likely_sql(sql_str):
|
|
252
|
+
if not sql_str or not self.is_likely_sql(sql_str):
|
|
251
253
|
return None
|
|
252
254
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
try:
|
|
256
|
+
formatted_sql = format_sql_code(
|
|
257
|
+
sql_str,
|
|
258
|
+
self.dialect,
|
|
259
|
+
self.config,
|
|
260
|
+
placeholders=placeholders,
|
|
261
|
+
force_single_line=force_single_line,
|
|
262
|
+
)
|
|
263
|
+
except SQLFormattingError:
|
|
264
|
+
return None
|
|
260
265
|
|
|
261
266
|
if placeholders:
|
|
262
267
|
for placeholder in placeholders.keys():
|
|
268
|
+
if not placeholder: # Skip empty placeholders
|
|
269
|
+
continue
|
|
263
270
|
# Remove quotes around placeholders in formatted SQL
|
|
264
271
|
formatted_sql = formatted_sql.replace(f"'{placeholder}'", placeholder)
|
|
265
272
|
formatted_sql = formatted_sql.replace(f'"{placeholder}"', placeholder)
|
|
@@ -268,31 +275,36 @@ class SQLStringFormatter(ast.NodeTransformer):
|
|
|
268
275
|
self.changed = True
|
|
269
276
|
# Reconstruct the f-string
|
|
270
277
|
new_values = []
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
278
|
+
if not placeholders:
|
|
279
|
+
# If no placeholders, just create a simple f-string
|
|
280
|
+
new_values.append(ast.Constant(value=formatted_sql))
|
|
281
|
+
else:
|
|
282
|
+
pattern = re.compile('|'.join(re.escape(k) for k in placeholders.keys() if k))
|
|
283
|
+
idx = 0
|
|
284
|
+
while idx < len(formatted_sql):
|
|
285
|
+
match = pattern.search(formatted_sql, idx)
|
|
286
|
+
if match:
|
|
287
|
+
if match.start() > idx:
|
|
288
|
+
# Add preceding text as Constant
|
|
289
|
+
new_values.append(ast.Constant(value=formatted_sql[idx:match.start()]))
|
|
290
|
+
placeholder = match.group()
|
|
291
|
+
expr_str = placeholders[placeholder].strip()
|
|
292
|
+
try:
|
|
293
|
+
expr_ast = ast.parse(expr_str, mode='eval').body
|
|
294
|
+
except SyntaxError as e:
|
|
295
|
+
self.logger.warning(f"Failed to parse expression '{expr_str}': {e}")
|
|
296
|
+
return None
|
|
297
|
+
new_values.append(ast.FormattedValue(
|
|
298
|
+
value=expr_ast,
|
|
299
|
+
conversion=-1,
|
|
300
|
+
format_spec=None
|
|
301
|
+
))
|
|
302
|
+
idx = match.end()
|
|
303
|
+
else:
|
|
304
|
+
# Add the rest of the string as Constant
|
|
305
|
+
new_values.append(ast.Constant(value=formatted_sql[idx:]))
|
|
306
|
+
break
|
|
307
|
+
|
|
296
308
|
new_node = ast.JoinedStr(values=new_values)
|
|
297
309
|
# Wrap the f-string with adjusted triple quotes
|
|
298
310
|
if '\n' in formatted_sql:
|
|
@@ -310,6 +322,9 @@ class SQLStringFormatter(ast.NodeTransformer):
|
|
|
310
322
|
except SQLFormattingError as e:
|
|
311
323
|
self.logger.warning(f"SQL formatting skipped: {e}")
|
|
312
324
|
return None
|
|
325
|
+
except Exception as e:
|
|
326
|
+
self.logger.warning(f"Unexpected error during SQL formatting: {e}")
|
|
327
|
+
return None
|
|
313
328
|
|
|
314
329
|
def visit_Assign(self, node: ast.Assign) -> ast.AST:
|
|
315
330
|
"""Handles assignments."""
|
|
@@ -456,7 +471,7 @@ def process_notebook(
|
|
|
456
471
|
return changed
|
|
457
472
|
|
|
458
473
|
except Exception as e:
|
|
459
|
-
logger.error(f"Failed to process notebook {notebook_path}: {e}")
|
|
474
|
+
logger.error(f"Failed to process notebook {notebook_path}: {e}", exc_info=True)
|
|
460
475
|
raise
|
|
461
476
|
|
|
462
477
|
|
|
@@ -487,16 +502,26 @@ def main():
|
|
|
487
502
|
try:
|
|
488
503
|
config = load_config(args.config)
|
|
489
504
|
changed = False
|
|
505
|
+
changed_files = []
|
|
490
506
|
|
|
491
507
|
for notebook in args.notebooks:
|
|
492
508
|
if process_notebook(notebook, config, args.dialect, logger):
|
|
493
509
|
changed = True
|
|
510
|
+
changed_files.append(notebook)
|
|
511
|
+
|
|
512
|
+
# Print summary
|
|
513
|
+
if changed:
|
|
514
|
+
logger.info("Changes made to the following notebooks:")
|
|
515
|
+
for file in changed_files:
|
|
516
|
+
logger.info(f" - {file}")
|
|
517
|
+
else:
|
|
518
|
+
logger.info("No changes needed. All notebooks are properly formatted.")
|
|
494
519
|
|
|
495
|
-
sys.exit(0
|
|
520
|
+
sys.exit(0)
|
|
496
521
|
|
|
497
522
|
except Exception as e:
|
|
498
523
|
logger.error(f"Fatal error: {e}")
|
|
499
|
-
sys.exit(
|
|
524
|
+
sys.exit(1)
|
|
500
525
|
|
|
501
526
|
|
|
502
527
|
if __name__ == "__main__":
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sqlnbfmt
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A tool to format SQL code in Jupyter notebooks.
|
|
5
5
|
Author-email: Qing <qingye779@gmail.com>
|
|
6
6
|
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
7
9
|
Requires-Python: >=3.12
|
|
8
10
|
Description-Content-Type: text/markdown
|
|
9
11
|
License-File: LICENSE
|
|
@@ -114,6 +114,28 @@ WHERE
|
|
|
114
114
|
[nbf.new_code_cell('''message = "This is not an SQL query: select * from users;"''')],
|
|
115
115
|
[nbf.new_code_cell('''message = "This is not an SQL query: select * from users;"''')],
|
|
116
116
|
),
|
|
117
|
+
# Test Case 11: F-string without variables
|
|
118
|
+
(
|
|
119
|
+
[nbf.new_code_cell('''query = f"select id, name from users where active = 1"''')],
|
|
120
|
+
[nbf.new_code_cell('''query = f"""
|
|
121
|
+
SELECT
|
|
122
|
+
id,
|
|
123
|
+
name
|
|
124
|
+
FROM users
|
|
125
|
+
WHERE
|
|
126
|
+
active = 1
|
|
127
|
+
"""''')],
|
|
128
|
+
),
|
|
129
|
+
# Test Case 12: F-string with only string constants
|
|
130
|
+
(
|
|
131
|
+
[nbf.new_code_cell('''query = f"select " f"id, name " f"from users"''')],
|
|
132
|
+
[nbf.new_code_cell('''query = f"""
|
|
133
|
+
SELECT
|
|
134
|
+
id,
|
|
135
|
+
name
|
|
136
|
+
FROM users
|
|
137
|
+
"""''')],
|
|
138
|
+
),
|
|
117
139
|
])
|
|
118
140
|
def test_sql_formatter(temp_nb_path, logger, input_cells, expected_cells):
|
|
119
141
|
# Create a new notebook
|
|
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
|