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.
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sqlnbfmt
3
- Version: 0.1.2
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.2"
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
- formatted_sql = format_sql_code(
254
- sql_str,
255
- self.dialect,
256
- self.config,
257
- placeholders=placeholders,
258
- force_single_line=force_single_line,
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
- pattern = re.compile('|'.join(re.escape(k) for k in placeholders.keys()))
272
- idx = 0
273
- while idx < len(formatted_sql):
274
- match = pattern.search(formatted_sql, idx)
275
- if match:
276
- if match.start() > idx:
277
- # Add preceding text as Constant
278
- new_values.append(ast.Constant(value=formatted_sql[idx:match.start()]))
279
- placeholder = match.group()
280
- expr_str = placeholders[placeholder].strip()
281
- try:
282
- expr_ast = ast.parse(expr_str, mode='eval').body
283
- except SyntaxError as e:
284
- self.logger.warning(f"Failed to parse expression '{expr_str}': {e}")
285
- return None
286
- new_values.append(ast.FormattedValue(
287
- value=expr_ast,
288
- conversion=-1,
289
- format_spec=None
290
- ))
291
- idx = match.end()
292
- else:
293
- # Add the rest of the string as Constant
294
- new_values.append(ast.Constant(value=formatted_sql[idx:]))
295
- break
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 if changed else 1)
520
+ sys.exit(0)
496
521
 
497
522
  except Exception as e:
498
523
  logger.error(f"Fatal error: {e}")
499
- sys.exit(2)
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.2
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