jupyter-duckdb 1.2.0.4__tar.gz → 1.2.0.5__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.
Files changed (93) hide show
  1. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/PKG-INFO +12 -2
  2. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/README.md +11 -1
  3. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/kernel.py +45 -3
  4. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommand.py +34 -10
  5. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandCallback.py +14 -3
  6. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandHandler.py +47 -6
  7. jupyter_duckdb-1.2.0.5/src/duckdb_kernel/magics/StringWrapper.py +3 -0
  8. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/DCParser.py +10 -7
  9. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/LogicParser.py +6 -6
  10. jupyter_duckdb-1.2.0.5/src/duckdb_kernel/parser/ParserError.py +18 -0
  11. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/RAParser.py +12 -14
  12. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/__init__.py +1 -0
  13. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/DCOperand.py +7 -4
  14. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +14 -6
  15. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/PKG-INFO +12 -2
  16. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/SOURCES.txt +2 -0
  17. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/test/test_ra.py +2 -2
  18. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/setup.cfg +0 -0
  19. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/setup.py +0 -0
  20. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/__init__.py +0 -0
  21. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/__main__.py +0 -0
  22. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Column.py +0 -0
  23. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Connection.py +0 -0
  24. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Constraint.py +0 -0
  25. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/DatabaseError.py +0 -0
  26. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/ForeignKey.py +0 -0
  27. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/Table.py +0 -0
  28. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/__init__.py +0 -0
  29. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/error/EmptyResultError.py +0 -0
  30. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/error/__init__.py +0 -0
  31. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/duckdb/Connection.py +0 -0
  32. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/duckdb/__init__.py +0 -0
  33. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/postgres/Connection.py +0 -0
  34. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/postgres/__init__.py +0 -0
  35. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/postgres/util.py +0 -0
  36. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/sqlite/Connection.py +0 -0
  37. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/db/implementation/sqlite/__init__.py +0 -0
  38. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/kernel.json +0 -0
  39. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
  40. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/magics/__init__.py +0 -0
  41. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
  42. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
  43. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
  44. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
  45. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAElement.py +0 -0
  46. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
  47. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
  48. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
  49. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
  50. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
  51. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
  52. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
  53. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Cross.py +0 -0
  54. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
  55. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
  56. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Division.py +0 -0
  57. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
  58. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
  59. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
  60. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
  61. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Join.py +0 -0
  62. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
  63. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
  64. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
  65. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
  66. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
  67. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
  68. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
  69. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
  70. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
  71. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
  72. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
  73. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
  74. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
  75. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
  76. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
  77. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
  78. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
  79. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
  80. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/parser/util/__init__.py +0 -0
  81. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/ResultSetComparator.py +0 -0
  82. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/TestError.py +0 -0
  83. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/__init__.py +0 -0
  84. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/util/formatting.py +0 -0
  85. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/Drawer.py +0 -0
  86. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
  87. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
  88. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/duckdb_kernel/visualization/__init__.py +0 -0
  89. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
  90. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
  91. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
  92. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/test/test_dc.py +0 -0
  93. {jupyter_duckdb-1.2.0.4 → jupyter_duckdb-1.2.0.5}/test/test_result_comparison.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jupyter-duckdb
3
- Version: 1.2.0.4
3
+ Version: 1.2.0.5
4
4
  Summary: a basic wrapper kernel for DuckDB
5
5
  Home-page: https://github.com/erictroebs/jupyter-duckdb
6
6
  Author: Eric Tröbs
@@ -262,7 +262,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
262
262
 
263
263
  ```
264
264
  %RA
265
- π a, b (σ c = 1 (R))
265
+ π [a, b][c = 1] (R))
266
266
  ```
267
267
 
268
268
  The supported operations are:
@@ -294,3 +294,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
294
294
  %DC
295
295
  { a, b | R(a, b, c) ∧ c = 1 }
296
296
  ```
297
+
298
+ ### Automated Parser Selection
299
+
300
+ `%ALL_RA` or `%ALL_DC` enables the corresponding parser for all subsequently
301
+ executed cells.
302
+
303
+ If the magic command `%AUTO_PARSER` is added to a cell, a parser is
304
+ automatically selected. If `%GUESS_PARSER` is executed, the parser is
305
+ automatically selected for all subsequent cells.
306
+
@@ -234,7 +234,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
234
234
 
235
235
  ```
236
236
  %RA
237
- π a, b (σ c = 1 (R))
237
+ π [a, b][c = 1] (R))
238
238
  ```
239
239
 
240
240
  The supported operations are:
@@ -266,3 +266,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
266
266
  %DC
267
267
  { a, b | R(a, b, c) ∧ c = 1 }
268
268
  ```
269
+
270
+ ### Automated Parser Selection
271
+
272
+ `%ALL_RA` or `%ALL_DC` enables the corresponding parser for all subsequently
273
+ executed cells.
274
+
275
+ If the magic command `%AUTO_PARSER` is added to a cell, a parser is
276
+ automatically selected. If `%GUESS_PARSER` is executed, the parser is
277
+ automatically selected for all subsequent cells.
278
+
@@ -12,7 +12,7 @@ from ipykernel.kernelbase import Kernel
12
12
  from .db import Connection, DatabaseError, Table
13
13
  from .db.error import *
14
14
  from .magics import *
15
- from .parser import RAParser, DCParser
15
+ from .parser import RAParser, DCParser, ParserError
16
16
  from .util.ResultSetComparator import ResultSetComparator
17
17
  from .util.TestError import TestError
18
18
  from .util.formatting import row_count, rows_table, wrap_image
@@ -46,8 +46,12 @@ class DuckDBKernel(Kernel):
46
46
  MagicCommand('query_max_rows').arg('count').on(self._query_max_rows_magic),
47
47
  MagicCommand('schema').flag('td').opt('only').on(self._schema_magic),
48
48
  MagicCommand('store').arg('file').flag('noheader').result(True).on(self._store_magic),
49
- MagicCommand('ra').flag('analyze').code(True).on(self._ra_magic),
50
- MagicCommand('dc').code(True).on(self._dc_magic)
49
+ MagicCommand('ra').disable('dc', 'auto_parser').flag('analyze').code(True).on(self._ra_magic),
50
+ MagicCommand('all_ra').arg('value', '1').on(self._all_ra_magic),
51
+ MagicCommand('dc').disable('ra', 'auto_parser').code(True).on(self._dc_magic),
52
+ MagicCommand('all_dc').arg('value', '1').on(self._all_dc_magic),
53
+ MagicCommand('auto_parser').disable('ra', 'dc').code(True).on(self._auto_parser_magic),
54
+ MagicCommand('guess_parser').arg('value', '1').on(self._guess_parser_magic),
51
55
  )
52
56
 
53
57
  # create placeholders for database and tests
@@ -496,6 +500,15 @@ class DuckDBKernel(Kernel):
496
500
  'generated_code': sql
497
501
  }
498
502
 
503
+ def _all_ra_magic(self, silent: bool, value: str):
504
+ if value.lower() in ('1', 'on', 'true'):
505
+ self._magics['ra'].default(True)
506
+ self._magics['dc'].default(False)
507
+
508
+ self.print('All further cells are interpreted as %RA.\n')
509
+ else:
510
+ self._magics['ra'].default(False)
511
+
499
512
  def _dc_magic(self, silent: bool, code: str):
500
513
  if self._db is None:
501
514
  raise AssertionError('load a database first')
@@ -520,6 +533,35 @@ class DuckDBKernel(Kernel):
520
533
  'column_name_mapping': cnm
521
534
  }
522
535
 
536
+ def _all_dc_magic(self, silent: bool, value: str):
537
+ if value.lower() in ('1', 'on', 'true'):
538
+ self._magics['dc'].default(True)
539
+ self._magics['ra'].default(False)
540
+
541
+ self.print('All further cells are interpreted as %DC.\n')
542
+ else:
543
+ self._magics['dc'].default(False)
544
+
545
+ def _guess_parser_magic(self, silent: bool, value: str):
546
+ if value.lower() in ('1', 'on', 'true'):
547
+ self._magics['auto_parser'].default(True)
548
+ self.print('The correct parser is guessed for each subsequently executed cell.\n')
549
+ else:
550
+ self._magics['auto_parser'].default(False)
551
+
552
+ def _auto_parser_magic(self, silent: bool, code: str):
553
+ try:
554
+ return self._ra_magic(silent, code, analyze=False)
555
+ except ParserError as e:
556
+ if e.depth > 0:
557
+ raise e
558
+
559
+ try:
560
+ return self._dc_magic(silent, code)
561
+ except ParserError as e:
562
+ if e.depth > 0:
563
+ raise e
564
+
523
565
  # jupyter related functions
524
566
  def do_execute(self, code: str, silent: bool,
525
567
  store_history: bool = True, user_expressions: dict = None, allow_stdin: bool = False,
@@ -1,27 +1,28 @@
1
- from typing import Any, List, Tuple, Callable, Dict
1
+ from typing import Any, List, Tuple, Callable, Dict, Set
2
2
 
3
3
 
4
4
  class MagicCommand:
5
- _ARG = '''([^ ]+?|'.+?'|".+?")'''
5
+ _ARG = '''([^ ]+?|'.+?'|".+?")?'''
6
6
 
7
7
  def __init__(self, *names: str):
8
- self._names: Tuple[str] = names
8
+ self._names: Tuple[str, ...] = names
9
9
 
10
- self._arguments: List[Tuple[str, str]] = []
10
+ self._arguments: List[Tuple[str, Any, str]] = []
11
11
  self._flags: List[Tuple[str, str]] = []
12
12
  self._optionals: List[Tuple[str, Any, str]] = []
13
-
13
+ self._disables: Set[str] = set()
14
14
  self._code: bool = False
15
15
  self._result: bool = False
16
+ self._default: bool = False
16
17
 
17
18
  self._on: List[Callable] = []
18
19
 
19
20
  @property
20
- def names(self) -> Tuple[str]:
21
+ def names(self) -> Tuple[str, ...]:
21
22
  return self._names
22
23
 
23
24
  @property
24
- def args(self) -> List[Tuple[str, str]]:
25
+ def args(self) -> List[Tuple[str, Any, str]]:
25
26
  return self._arguments
26
27
 
27
28
  @property
@@ -32,6 +33,10 @@ class MagicCommand:
32
33
  def optionals(self) -> List[Tuple[str, Any, str]]:
33
34
  return self._optionals
34
35
 
36
+ @property
37
+ def disables(self) -> Set[str]:
38
+ return self._disables
39
+
35
40
  @property
36
41
  def requires_code(self) -> bool:
37
42
  return self._code
@@ -40,8 +45,17 @@ class MagicCommand:
40
45
  def requires_query_result(self) -> bool:
41
46
  return self._result
42
47
 
43
- def arg(self, name: str, description: str = None) -> 'MagicCommand':
44
- self._arguments.append((name, description))
48
+ @property
49
+ def is_default(self) -> bool:
50
+ return self._default
51
+
52
+ def arg(self, name: str, default_value: Any = None, description: str = None) -> 'MagicCommand':
53
+ if len(self._arguments) > 0:
54
+ ln, ldv, _ = self._arguments[-1]
55
+ if ldv is not None and default_value is None:
56
+ raise ValueError(f'argument {name} without default value registered after argument {ln} with default value {ldv}')
57
+
58
+ self._arguments.append((name, default_value, description))
45
59
  return self
46
60
 
47
61
  def opt(self, name: str, default_value: Any = None, description: str = None) -> 'MagicCommand':
@@ -52,6 +66,12 @@ class MagicCommand:
52
66
  self._flags.append((name, description))
53
67
  return self
54
68
 
69
+ def disable(self, *name: str) -> 'MagicCommand':
70
+ for n in name:
71
+ self._disables.add(n)
72
+
73
+ return self
74
+
55
75
  def code(self, code: bool) -> 'MagicCommand':
56
76
  self._code = code
57
77
  return self
@@ -60,10 +80,14 @@ class MagicCommand:
60
80
  self._result = result
61
81
  return self
62
82
 
63
- def on(self, fun: Callable):
83
+ def on(self, fun: Callable) -> 'MagicCommand':
64
84
  self._on.append(fun)
65
85
  return self
66
86
 
87
+ def default(self, default: bool) -> 'MagicCommand':
88
+ self._default = default
89
+ return self
90
+
67
91
  @property
68
92
  def parameters(self) -> str:
69
93
  args = ' +'.join([self._ARG] * len(self._arguments))
@@ -1,20 +1,31 @@
1
1
  from typing import Optional, List
2
2
 
3
3
  from . import MagicCommand
4
+ from .StringWrapper import StringWrapper
4
5
 
5
6
 
6
7
  class MagicCommandCallback:
7
- def __init__(self, mc: MagicCommand, silent: bool, code: str, *args, **kwargs):
8
+ def __init__(self, mc: MagicCommand, silent: bool, code: StringWrapper, *args, **kwargs):
8
9
  self._mc: MagicCommand = mc
9
10
  self._silent: bool = silent
10
- self._code: str = code
11
+ self._code: StringWrapper = code
11
12
  self._args = args
12
13
  self._kwargs = kwargs
13
14
 
15
+ @property
16
+ def magic(self) -> MagicCommand:
17
+ return self._mc
18
+
14
19
  def __call__(self, columns: Optional[List[str]] = None, rows: Optional[List[List]] = None):
15
20
  if self._mc.requires_code:
16
- return self._mc(self._silent, self._code, *self._args, **self._kwargs)
21
+ result = self._mc(self._silent, self._code.value, *self._args, **self._kwargs)
22
+ if 'generated_code' in result:
23
+ self._code.value = result['generated_code']
24
+
25
+ return result
26
+
17
27
  if self._mc.requires_query_result:
18
28
  return self._mc(self._silent, columns, rows, *self._args, **self._kwargs)
29
+
19
30
  else:
20
31
  return self._mc(self._silent, *self._args, **self._kwargs)
@@ -1,7 +1,8 @@
1
1
  import re
2
- from typing import Dict, Tuple, List
2
+ from typing import Dict, Tuple, List, Optional
3
3
 
4
4
  from . import MagicCommand, MagicCommandException, MagicCommandCallback
5
+ from .StringWrapper import StringWrapper
5
6
 
6
7
 
7
8
  class MagicCommandHandler:
@@ -14,10 +15,23 @@ class MagicCommandHandler:
14
15
  key = key.lower()
15
16
  self._magics[key] = cmd
16
17
 
18
+ def __getitem__(self, key: str) -> MagicCommand:
19
+ return self._magics[key.lower()]
20
+
17
21
  def __call__(self, silent: bool, code: str) -> Tuple[str, List[MagicCommandCallback], List[MagicCommandCallback]]:
18
- pre_query_callbacks = []
19
- post_query_callbacks = []
22
+ code_wrapper = StringWrapper()
23
+ enabled_callbacks: List[MagicCommandCallback] = []
24
+
25
+ # enable commands with default==True
26
+ for magic in self._magics.values():
27
+ if magic.is_default:
28
+ flags = {name: False for name, _ in magic.flags}
29
+ optionals = {name: default for name, default, _ in magic.optionals}
30
+ callback = MagicCommandCallback(magic, silent, code_wrapper, **flags, **optionals)
20
31
 
32
+ enabled_callbacks.append(callback)
33
+
34
+ # search for magic commands in code
21
35
  while True:
22
36
  # ensure code starts with '%' or '%%' but not with '%%%'
23
37
  match = re.match(r'^%{1,2}([^% ]+?)([ \t]*$| .+?$)', code, re.MULTILINE | re.IGNORECASE)
@@ -45,7 +59,11 @@ class MagicCommandHandler:
45
59
  raise MagicCommandException(f'could not parse parameters for command "{command}"')
46
60
 
47
61
  # extract args
48
- args = [g for g, _ in zip(match.groups(), magic.args)]
62
+ args = [group if group is not None else default
63
+ for group, (_, default, _) in zip(match.groups(), magic.args)]
64
+
65
+ if any(arg is None for arg in args):
66
+ raise MagicCommandException(f'could not parse parameters for command "{command}"')
49
67
 
50
68
  i = len(args) + 1
51
69
 
@@ -73,12 +91,35 @@ class MagicCommandHandler:
73
91
  optionals[name.lower()] = value
74
92
 
75
93
  # add to callbacks
76
- callback = MagicCommandCallback(magic, silent, code, *args, **flags, **optionals)
94
+ callback = MagicCommandCallback(magic, silent, code_wrapper, *args, **flags, **optionals)
95
+ enabled_callbacks.append(callback)
96
+
97
+ # disable overwritten callbacks
98
+ callbacks = []
99
+ blacklist = set()
100
+
101
+ for callback in reversed(enabled_callbacks):
102
+ for name in callback.magic.names:
103
+ if name in blacklist:
104
+ break
105
+ else:
106
+ callbacks.append(callback)
107
+
108
+ for name in callback.magic.names:
109
+ blacklist.add(name)
110
+ for disable in callback.magic.disables:
111
+ blacklist.add(disable)
112
+
113
+ # prepare callback lists
114
+ pre_query_callbacks = []
115
+ post_query_callbacks = []
77
116
 
78
- if not magic.requires_query_result:
117
+ for callback in reversed(callbacks):
118
+ if not callback.magic.requires_query_result:
79
119
  pre_query_callbacks.append(callback)
80
120
  else:
81
121
  post_query_callbacks.append(callback)
82
122
 
83
123
  # return callbacks
124
+ code_wrapper.value = code
84
125
  return code, pre_query_callbacks, post_query_callbacks
@@ -0,0 +1,3 @@
1
+ class StringWrapper:
2
+ def __init__(self, value: str = None):
3
+ self.value: str = value
@@ -1,3 +1,4 @@
1
+ from .ParserError import DCParserError
1
2
  from .elements import *
2
3
  from .tokenizer import *
3
4
 
@@ -18,17 +19,17 @@ class DCParser:
18
19
  )
19
20
 
20
21
  # raise exception if query is not in the correct format
21
- raise AssertionError('The expression shall be of the format "{ x1, ..., xn | f(x1, ..., xn) }".')
22
+ raise DCParserError('The expression shall be of the format "{ x1, ..., xn | f(x1, ..., xn) }".', 0)
22
23
 
23
24
  @staticmethod
24
- def parse_projection(*tokens: Token) -> LogicOperand:
25
+ def parse_projection(*tokens: Token, depth: int = 0) -> LogicOperand:
25
26
  if len(tokens) == 1:
26
27
  tokens = tuple(Tokenizer.tokenize(tokens[0]))
27
28
 
28
29
  return LogicOperand(*tokens)
29
30
 
30
31
  @staticmethod
31
- def parse_condition(*tokens: Token) -> LogicElement:
32
+ def parse_condition(*tokens: Token, depth: int = 0) -> LogicElement:
32
33
  if len(tokens) == 1:
33
34
  tokens = tuple(Tokenizer.tokenize(tokens[0]))
34
35
 
@@ -40,8 +41,8 @@ class DCParser:
40
41
  # return the operator
41
42
  # with left part of tokens and right part of tokens
42
43
  return operator(
43
- DCParser.parse_condition(*tokens[:-i]),
44
- DCParser.parse_condition(*tokens[-i + 1:])
44
+ DCParser.parse_condition(*tokens[:-i], depth=depth + 1),
45
+ DCParser.parse_condition(*tokens[-i + 1:], depth=depth + 1)
45
46
  )
46
47
 
47
48
  # not
@@ -56,10 +57,12 @@ class DCParser:
56
57
  elif len(tokens) == 2:
57
58
  return DCOperand(
58
59
  tokens[0],
59
- tuple(Tokenizer.tokenize(tokens[1]))
60
+ tuple(Tokenizer.tokenize(tokens[1])),
61
+ depth=depth + 1
60
62
  )
61
63
  else:
62
64
  return DCOperand(
63
65
  tokens[0],
64
- tokens[1:]
66
+ tokens[1:],
67
+ depth=depth + 1
65
68
  )
@@ -4,12 +4,12 @@ from .tokenizer import *
4
4
 
5
5
  class LogicParser:
6
6
  @staticmethod
7
- def parse_query(query: str) -> LogicElement:
7
+ def parse_query(query: str, depth: int = 0) -> LogicElement:
8
8
  initial_token = Token(query)
9
- return LogicParser.parse_tokens(initial_token)
9
+ return LogicParser.parse_tokens(initial_token, depth=depth)
10
10
 
11
11
  @staticmethod
12
- def parse_tokens(*tokens: Token) -> LogicElement:
12
+ def parse_tokens(*tokens: Token, depth: int = 0) -> LogicElement:
13
13
  if len(tokens) == 1:
14
14
  tokens = tuple(Tokenizer.tokenize(tokens[0]))
15
15
 
@@ -21,14 +21,14 @@ class LogicParser:
21
21
  # return the operator
22
22
  # with left part of tokens and right part of tokens
23
23
  return operator(
24
- LogicParser.parse_tokens(*tokens[:-i]),
25
- LogicParser.parse_tokens(*tokens[-i + 1:])
24
+ LogicParser.parse_tokens(*tokens[:-i], depth=depth + 1),
25
+ LogicParser.parse_tokens(*tokens[-i + 1:], depth=depth + 1)
26
26
  )
27
27
 
28
28
  # not
29
29
  if tokens[0] in LOGIC_NOT.symbols():
30
30
  return LOGIC_NOT(
31
- LogicParser.parse_tokens(*tokens[1:])
31
+ LogicParser.parse_tokens(*tokens[1:], depth=depth + 1)
32
32
  )
33
33
 
34
34
  # ArgList
@@ -0,0 +1,18 @@
1
+ class ParserError(Exception):
2
+ def __init__(self, message: str, depth: int):
3
+ super().__init__(message)
4
+
5
+ self.message: str = message
6
+ self.depth: int = depth
7
+
8
+
9
+ class RAParserError(ParserError):
10
+ pass
11
+
12
+
13
+ class DCParserError(ParserError):
14
+ pass
15
+
16
+
17
+ class LogicParserError(ParserError):
18
+ pass
@@ -1,4 +1,5 @@
1
1
  from .LogicParser import LogicParser
2
+ from .ParserError import RAParserError
2
3
  from .elements import *
3
4
  from .tokenizer import *
4
5
 
@@ -10,10 +11,10 @@ class RAParser:
10
11
  @staticmethod
11
12
  def parse_query(query: str) -> RAElement:
12
13
  initial_token = Token(query)
13
- return RAParser.parse_tokens(initial_token)
14
+ return RAParser.parse_tokens(initial_token, depth=0)
14
15
 
15
16
  @staticmethod
16
- def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None) -> RAElement:
17
+ def parse_tokens(*tokens: Token, target: RAOperator | RAOperand = None, depth: int = 0) -> RAElement:
17
18
  if len(tokens) == 1:
18
19
  tokens = tuple(Tokenizer.tokenize(tokens[0]))
19
20
 
@@ -24,15 +25,15 @@ class RAParser:
24
25
  if tokens[-i].lower() in operator.symbols():
25
26
  # raise error if left or right operand missing
26
27
  if i == 1:
27
- raise AssertionError(f'right operand missing after {tokens[-i]}')
28
+ raise RAParserError(f'right operand missing after {tokens[-i]}', depth)
28
29
  if i == len(tokens):
29
- raise AssertionError(f'left operand missing before {tokens[-i]}')
30
+ raise RAParserError(f'left operand missing before {tokens[-i]}', depth)
30
31
 
31
32
  # return the operator
32
33
  # with left part of tokens and right part of tokens
33
34
  return operator(
34
- RAParser.parse_tokens(*tokens[:-i]),
35
- RAParser.parse_tokens(*tokens[-i + 1:])
35
+ RAParser.parse_tokens(*tokens[:-i], depth=depth + 1),
36
+ RAParser.parse_tokens(*tokens[-i + 1:], depth=depth + 1)
36
37
  )
37
38
 
38
39
  # unary operators
@@ -44,8 +45,8 @@ class RAParser:
44
45
  # the last token is the operators target.
45
46
  if target is None:
46
47
  op = operator(
47
- RAParser.parse_tokens(tokens[-1]),
48
- LogicParser.parse_tokens(*tokens[-i + 1:-1])
48
+ RAParser.parse_tokens(tokens[-1], depth=depth + 1),
49
+ LogicParser.parse_tokens(*tokens[-i + 1:-1], depth=depth + 1)
49
50
  )
50
51
 
51
52
  # Otherwise the handed target is this operator's
@@ -53,16 +54,13 @@ class RAParser:
53
54
  else:
54
55
  op = operator(
55
56
  target,
56
- LogicParser.parse_tokens(*tokens[-i + 1:])
57
+ LogicParser.parse_tokens(*tokens[-i + 1:], depth=depth + 1)
57
58
  )
58
59
 
59
60
  # If there are any more tokens the operator is
60
61
  # the target for the next step.
61
62
  if i < len(tokens):
62
- return RAParser.parse_tokens(
63
- *tokens[:-i],
64
- target=op
65
- )
63
+ return RAParser.parse_tokens(*tokens[:-i], target=op, depth=depth + 1)
66
64
 
67
65
  # Otherwise the operator is the return value.
68
66
  else:
@@ -70,6 +68,6 @@ class RAParser:
70
68
 
71
69
  # return as name
72
70
  if len(tokens) > 1:
73
- raise AssertionError(f'{tokens=}')
71
+ raise RAParserError(f'{tokens=}', depth)
74
72
 
75
73
  return RAOperand(tokens[0])
@@ -1,3 +1,4 @@
1
1
  from .DCParser import DCParser
2
2
  from .LogicParser import LogicParser
3
3
  from .RAParser import RAParser
4
+ from .ParserError import *
@@ -1,13 +1,14 @@
1
1
  from typing import Tuple
2
2
 
3
3
  from .LogicOperand import LogicOperand
4
+ from ..ParserError import DCParserError
4
5
  from ..tokenizer import Token
5
6
 
6
7
 
7
8
  class DCOperand(LogicOperand):
8
- def __new__(cls, relation: Token, columns: Tuple[Token], skip_comma: bool = False):
9
+ def __new__(cls, relation: Token, columns: Tuple[Token, ...], skip_comma: bool = False, depth: int = 0):
9
10
  if not skip_comma and not all(t == ',' for i, t in enumerate(columns) if i % 2 == 1):
10
- raise AssertionError('arguments must be separated by commas')
11
+ raise DCParserError('arguments must be separated by commas', 0)
11
12
 
12
13
  return tuple.__new__(
13
14
  cls,
@@ -18,9 +19,11 @@ class DCOperand(LogicOperand):
18
19
  ))
19
20
  )
20
21
 
21
- def __init__(self, *args, **kwargs):
22
+ def __init__(self, relation: Token, columns: Tuple[Token, ...], skip_comma: bool = False, depth: int = 0):
22
23
  super().__init__()
23
- self.invert = False
24
+
25
+ self.depth: int = depth
26
+ self.invert: bool = False
24
27
 
25
28
  @property
26
29
  def relation(self) -> Token:
@@ -8,6 +8,7 @@ from ..LogicElement import LogicElement
8
8
  from ..LogicOperand import LogicOperand
9
9
  from ..LogicOperator import LogicOperator
10
10
  from ..unary import Not
11
+ from ...ParserError import DCParserError
11
12
  from ...tokenizer import Token
12
13
  from ...util.RenamableColumnList import RenamableColumnList
13
14
  from ....db import Table
@@ -43,7 +44,10 @@ class ConditionalSet:
43
44
  # If a constant was found, we store the value and replace it with a random attribute name.
44
45
  constant = le.names[i]
45
46
  new_token = Token.random(constant)
46
- new_operand = DCOperand(le.relation, le.names[:i] + (new_token,) + le.names[i + 1:], skip_comma=True)
47
+ new_operand = DCOperand(le.relation,
48
+ le.names[:i] + (new_token,) + le.names[i + 1:],
49
+ skip_comma=True,
50
+ depth=le.depth)
47
51
 
48
52
  # We now need an equality comparison to ensure the introduced attribute is equal to the constant.
49
53
  equality = Equal(
@@ -121,7 +125,8 @@ class ConditionalSet:
121
125
  # Raise an exception if the given number of operands does not match
122
126
  # the number of attributes in the relation.
123
127
  if len(source_columns) != len(operand.names):
124
- raise AssertionError(f'invalid number of attributes for relation {operand.relation}')
128
+ raise DCParserError(f'invalid number of attributes for relation {operand.relation}',
129
+ depth=operand.depth)
125
130
 
126
131
  # Create a column list for this operand.
127
132
  rcl: RenamableColumnList = RenamableColumnList.from_iter(source_columns)
@@ -215,7 +220,8 @@ class ConditionalSet:
215
220
  if left_name != right_name:
216
221
  break
217
222
  else:
218
- raise AssertionError(f'could not build join for relation {left_name}')
223
+ raise DCParserError(f'could not build join for relation {left_name}',
224
+ depth=left_op.depth)
219
225
 
220
226
  join_tuple = min(left_name, right_name), max(left_name, right_name)
221
227
 
@@ -253,7 +259,7 @@ class ConditionalSet:
253
259
 
254
260
  # If no joins were discovered using this table, an exception is raised.
255
261
  if discovered_joins == 0:
256
- raise AssertionError('no common attributes found for join')
262
+ raise DCParserError('no common attributes found for join', depth=right_op.depth)
257
263
 
258
264
  # The joins have to be sorted in a topologic order starting from t0.
259
265
  used_relations: Set[str] = {'t0'}
@@ -287,7 +293,8 @@ class ConditionalSet:
287
293
  break
288
294
 
289
295
  else:
290
- raise AssertionError('no valid topologic order found for positive joins')
296
+ raise DCParserError('no valid topologic order found for positive joins',
297
+ depth=min(op.depth for _, _, op in relevant_positive))
291
298
 
292
299
  all_negative_conditions: Dict[str, List[str]] = {}
293
300
  all_negative_filters: Dict[str, List[str]] = {}
@@ -317,7 +324,8 @@ class ConditionalSet:
317
324
  used_relations.add(target_name)
318
325
  break
319
326
  else:
320
- raise AssertionError('no valid topologic order found for negative joins')
327
+ raise DCParserError('no valid topologic order found for negative joins',
328
+ depth=min(op.depth for _, _, op in relevant_negative))
321
329
 
322
330
  # Build the SQL statement.
323
331
  sql_select = ', '.join(select_columns[col] if col in select_columns else col
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jupyter-duckdb
3
- Version: 1.2.0.4
3
+ Version: 1.2.0.5
4
4
  Summary: a basic wrapper kernel for DuckDB
5
5
  Home-page: https://github.com/erictroebs/jupyter-duckdb
6
6
  Author: Eric Tröbs
@@ -262,7 +262,7 @@ magic command `RA` activates the relational algebra mode for a single cell:
262
262
 
263
263
  ```
264
264
  %RA
265
- π a, b (σ c = 1 (R))
265
+ π [a, b][c = 1] (R))
266
266
  ```
267
267
 
268
268
  The supported operations are:
@@ -294,3 +294,13 @@ magic command `DC` activates the domain calculus mode for a single cell:
294
294
  %DC
295
295
  { a, b | R(a, b, c) ∧ c = 1 }
296
296
  ```
297
+
298
+ ### Automated Parser Selection
299
+
300
+ `%ALL_RA` or `%ALL_DC` enables the corresponding parser for all subsequently
301
+ executed cells.
302
+
303
+ If the magic command `%AUTO_PARSER` is added to a cell, a parser is
304
+ automatically selected. If `%GUESS_PARSER` is executed, the parser is
305
+ automatically selected for all subsequent cells.
306
+
@@ -24,9 +24,11 @@ src/duckdb_kernel/magics/MagicCommand.py
24
24
  src/duckdb_kernel/magics/MagicCommandCallback.py
25
25
  src/duckdb_kernel/magics/MagicCommandException.py
26
26
  src/duckdb_kernel/magics/MagicCommandHandler.py
27
+ src/duckdb_kernel/magics/StringWrapper.py
27
28
  src/duckdb_kernel/magics/__init__.py
28
29
  src/duckdb_kernel/parser/DCParser.py
29
30
  src/duckdb_kernel/parser/LogicParser.py
31
+ src/duckdb_kernel/parser/ParserError.py
30
32
  src/duckdb_kernel/parser/RAParser.py
31
33
  src/duckdb_kernel/parser/__init__.py
32
34
  src/duckdb_kernel/parser/elements/DCOperand.py
@@ -2,7 +2,7 @@ import pytest
2
2
 
3
3
  import duckdb_kernel.parser.elements.binary as BinaryOperators
4
4
  import duckdb_kernel.parser.elements.unary as UnaryOperators
5
- from duckdb_kernel.parser import RAParser
5
+ from duckdb_kernel.parser import RAParser, RAParserError
6
6
  from duckdb_kernel.parser.elements import RAOperand, LogicElement
7
7
  from . import Connection
8
8
 
@@ -644,7 +644,7 @@ def test_special_queries():
644
644
  with Connection() as con:
645
645
  # Consecutive operators triggered a recursion error in a previous
646
646
  # version, leading to an infinite loop / stack overflow.
647
- with pytest.raises(AssertionError, match='right operand missing after x'):
647
+ with pytest.raises(RAParserError, match='right operand missing after x'):
648
648
  RAParser.parse_query(r'''
649
649
  Users x x BannedUsers
650
650
  ''')