david8-duckdb 0.2.0b1__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.
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: david8_duckdb
3
+ Version: 0.2.0b1
4
+ Summary: SQL query builder. david8 duckdb dielect
5
+ Author-email: Danila Ganchar <danila.ganchar@gmail.com>
6
+ Maintainer-email: Danila Ganchar <danila.ganchar@gmail.com>
7
+ Project-URL: Homepage, https://github.com/d-ganchar/david8_duckdb
8
+ Project-URL: Changelog, https://github.com/d-ganchar/david8_duckdb/releases
9
+ Project-URL: Issues, https://github.com/d-ganchar/david8_duckdb/issues
10
+ Project-URL: CI, https://github.com/d-ganchar/david8_duckdb/actions
11
+ Project-URL: Documentation, https://github.com/d-ganchar/david8_duckdb/wiki
12
+ Project-URL: Source, https://github.com/d-ganchar/david8_duckdb
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Database
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: <3.14,>=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: david8<2.0.0b1,>=1.0.0b1
24
+
25
+ # david8_duckdb
26
+
27
+ `david8_duckdb` is [DuckDb](https://duckdb.org/) dialect for [david8](https://github.com/d-ganchar/david8)
28
+
29
+ See [Wiki](https://github.com/d-ganchar/david8_duckdb/wiki)
@@ -0,0 +1,5 @@
1
+ # david8_duckdb
2
+
3
+ `david8_duckdb` is [DuckDb](https://duckdb.org/) dialect for [david8](https://github.com/d-ganchar/david8)
4
+
5
+ See [Wiki](https://github.com/d-ganchar/david8_duckdb/wiki)
@@ -0,0 +1,10 @@
1
+ from david8.core.base_dialect import BaseDialect as _BaseDialect
2
+ from david8.param_styles import QMarkParamStyle
3
+
4
+ from .core.query_builder import DuckDbQueryBuilder as _QueryBuilder
5
+ from .protocols.query_builder import QueryBuilderProtocol
6
+
7
+
8
+ def get_qb(is_quote_mode: bool = False) -> QueryBuilderProtocol:
9
+ dialect = _BaseDialect(QMarkParamStyle(), is_quote_mode)
10
+ return _QueryBuilder(dialect)
File without changes
@@ -0,0 +1,29 @@
1
+ import dataclasses
2
+
3
+ from david8.core.base_query import BaseQuery
4
+ from david8.protocols.dialect import DialectProtocol
5
+
6
+ from david8_duckdb.protocols.sql import SelectProtocol
7
+
8
+
9
+ @dataclasses.dataclass(slots=True)
10
+ class CopyToQuery(BaseQuery):
11
+ source: str | SelectProtocol
12
+ target: str
13
+ copy_options: dict = None
14
+
15
+ def _render_sql(self, dialect: DialectProtocol) -> str:
16
+ source = f'({self.source.get_sql(dialect)})' if isinstance(self.source, SelectProtocol) else self.source
17
+ return f"COPY {source} TO '{self.target}'"
18
+
19
+ def _render_sql_postfix(self, dialect: DialectProtocol) -> str:
20
+ if not self.copy_options:
21
+ return ''
22
+
23
+ option_items = []
24
+ for key, value in self.copy_options.items():
25
+ if value is not None:
26
+ fixed_value = str(value).lower() if isinstance(value, bool) else value
27
+ option_items.append(f'{key} {fixed_value}')
28
+
29
+ return f' ({", ".join(option_items)})'
@@ -0,0 +1,15 @@
1
+ from david8.core.base_query_builder import BaseQueryBuilder as _BaseQueryBuilder
2
+ from david8.protocols.sql import AliasedProtocol, ExprProtocol, FunctionProtocol, QueryProtocol
3
+
4
+ from ..protocols.query_builder import QueryBuilderProtocol
5
+ from ..protocols.sql import SelectProtocol
6
+ from .copy_to_query import CopyToQuery
7
+ from .select_query import DuckDbSelect
8
+
9
+
10
+ class DuckDbQueryBuilder(QueryBuilderProtocol, _BaseQueryBuilder):
11
+ def select(self, *args: str | AliasedProtocol | ExprProtocol | FunctionProtocol) -> SelectProtocol:
12
+ return DuckDbSelect(select_columns=args, dialect=self._dialect)
13
+
14
+ def copy_to(self, source: str | SelectProtocol, target: str, copy_options: dict = None) -> QueryProtocol:
15
+ return CopyToQuery(dialect=self._dialect, source=source, target=target, copy_options=copy_options)
@@ -0,0 +1,50 @@
1
+ import dataclasses
2
+
3
+ from david8.core.base_dql import BaseSelect as _BaseSelect
4
+ from david8.protocols.dialect import DialectProtocol
5
+
6
+ from ..protocols.sql import SelectProtocol
7
+
8
+
9
+ @dataclasses.dataclass(slots=True)
10
+ class DuckDbSelect(_BaseSelect, SelectProtocol):
11
+ from_files: tuple[str, tuple[str, ...]] = dataclasses.field(default_factory=tuple)
12
+
13
+ def from_csv(self, *file_names: str) -> 'SelectProtocol':
14
+ self.from_files = ('read_csv', file_names)
15
+ return self
16
+
17
+ def from_json(self, *file_names: str) -> 'SelectProtocol':
18
+ self.from_files = ('read_json', file_names)
19
+ return self
20
+
21
+ def from_json_objects(self, *file_names: str) -> 'SelectProtocol':
22
+ self.from_files = ('read_json_objects', file_names)
23
+ return self
24
+
25
+ def from_ndjson_objects(self, *file_names: str) -> 'SelectProtocol':
26
+ self.from_files = ('read_ndjson_objects', file_names)
27
+ return self
28
+
29
+ def from_json_objects_auto(self, *file_names: str) -> 'SelectProtocol':
30
+ self.from_files = ('read_json_objects_auto', file_names)
31
+ return self
32
+
33
+ def from_parquet(self, *file_names: str) -> 'SelectProtocol':
34
+ self.from_files = ('read_parquet', file_names)
35
+ return self
36
+
37
+ def from_xlsx(self, file_name: str) -> 'SelectProtocol':
38
+ self.from_files = ('read_xlsx', (file_name,))
39
+ return self
40
+
41
+ def _from_to_sql(self, dialect: DialectProtocol) -> str:
42
+ if not self.from_files:
43
+ return _BaseSelect._from_to_sql(self, dialect)
44
+
45
+ if len(self.from_files[1]) > 1:
46
+ files = str(list(self.from_files[1]))
47
+ else:
48
+ files = f"'{self.from_files[1][0]}'"
49
+
50
+ return f' FROM {self.from_files[0]}({files})'
@@ -0,0 +1,19 @@
1
+ from david8.protocols.query_builder import QueryBuilderProtocol as _QueryBuilderProtocol
2
+ from david8.protocols.sql import AliasedProtocol, ExprProtocol, FunctionProtocol, QueryProtocol
3
+
4
+ from ..protocols.sql import SelectProtocol
5
+
6
+
7
+ class QueryBuilderProtocol(_QueryBuilderProtocol):
8
+ def select(self, *args: str | AliasedProtocol | ExprProtocol | FunctionProtocol) -> SelectProtocol:
9
+ pass
10
+
11
+ def copy_to(self, source: str | SelectProtocol, target: str, copy_options: dict = None) -> QueryProtocol:
12
+ """
13
+ :param source: query or table
14
+ :param target: file name
15
+ :param copy_options: https://duckdb.org/docs/stable/sql/statements/copy#copy--to-options
16
+
17
+ example:
18
+ qb.copy_to('events', 'events.parquet', {'FORMAT': 'parquet', 'DELIMITER': "','", 'APPEND': True})
19
+ """
@@ -0,0 +1,38 @@
1
+ from david8.protocols.sql import SelectProtocol as _SelectProtocol
2
+
3
+
4
+ class SelectProtocol(_SelectProtocol):
5
+ def from_csv(self, *file_names: str) -> 'SelectProtocol':
6
+ """
7
+ https://duckdb.org/docs/stable/data/csv/reading_faulty_csv_files
8
+ """
9
+
10
+ def from_json(self, *file_names: str) -> 'SelectProtocol':
11
+ """
12
+ https://duckdb.org/docs/stable/data/json/loading_json#the-read_json-function
13
+ """
14
+
15
+ def from_json_objects(self, *file_names: str) -> 'SelectProtocol':
16
+ """
17
+ https://duckdb.org/docs/stable/data/json/loading_json#functions-for-reading-json-objects
18
+ """
19
+
20
+ def from_ndjson_objects(self, *file_names: str) -> 'SelectProtocol':
21
+ """
22
+ https://duckdb.org/docs/stable/data/json/loading_json#functions-for-reading-json-objects
23
+ """
24
+
25
+ def from_json_objects_auto(self, *file_names: str) -> 'SelectProtocol':
26
+ """
27
+ https://duckdb.org/docs/stable/data/json/loading_json#functions-for-reading-json-objects
28
+ """
29
+
30
+ def from_parquet(self, *file_names: str) -> 'SelectProtocol':
31
+ """
32
+ read_parquet(): https://duckdb.org/docs/stable/data/parquet/overview
33
+ """
34
+
35
+ def from_xlsx(self, file_name: str) -> 'SelectProtocol':
36
+ """
37
+ read_xlsx(): https://duckdb.org/docs/stable/core_extensions/excel
38
+ """
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: david8_duckdb
3
+ Version: 0.2.0b1
4
+ Summary: SQL query builder. david8 duckdb dielect
5
+ Author-email: Danila Ganchar <danila.ganchar@gmail.com>
6
+ Maintainer-email: Danila Ganchar <danila.ganchar@gmail.com>
7
+ Project-URL: Homepage, https://github.com/d-ganchar/david8_duckdb
8
+ Project-URL: Changelog, https://github.com/d-ganchar/david8_duckdb/releases
9
+ Project-URL: Issues, https://github.com/d-ganchar/david8_duckdb/issues
10
+ Project-URL: CI, https://github.com/d-ganchar/david8_duckdb/actions
11
+ Project-URL: Documentation, https://github.com/d-ganchar/david8_duckdb/wiki
12
+ Project-URL: Source, https://github.com/d-ganchar/david8_duckdb
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Database
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: <3.14,>=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: david8<2.0.0b1,>=1.0.0b1
24
+
25
+ # david8_duckdb
26
+
27
+ `david8_duckdb` is [DuckDb](https://duckdb.org/) dialect for [david8](https://github.com/d-ganchar/david8)
28
+
29
+ See [Wiki](https://github.com/d-ganchar/david8_duckdb/wiki)
@@ -0,0 +1,17 @@
1
+ README.md
2
+ pyproject.toml
3
+ david8_duckdb/__init__.py
4
+ david8_duckdb.egg-info/PKG-INFO
5
+ david8_duckdb.egg-info/SOURCES.txt
6
+ david8_duckdb.egg-info/dependency_links.txt
7
+ david8_duckdb.egg-info/requires.txt
8
+ david8_duckdb.egg-info/top_level.txt
9
+ david8_duckdb/core/__init__.py
10
+ david8_duckdb/core/copy_to_query.py
11
+ david8_duckdb/core/query_builder.py
12
+ david8_duckdb/core/select_query.py
13
+ david8_duckdb/protocols/__init__.py
14
+ david8_duckdb/protocols/query_builder.py
15
+ david8_duckdb/protocols/sql.py
16
+ tests/test_copy_to.py
17
+ tests/test_select.py
@@ -0,0 +1 @@
1
+ david8<2.0.0b1,>=1.0.0b1
@@ -0,0 +1 @@
1
+ david8_duckdb
@@ -0,0 +1,83 @@
1
+ [project]
2
+ name = "david8_duckdb"
3
+ version = "0.2.0b1"
4
+ description = "SQL query builder. david8 duckdb dielect"
5
+ authors = [{name = "Danila Ganchar", email = "danila.ganchar@gmail.com"}]
6
+ maintainers = [{name = "Danila Ganchar", email = "danila.ganchar@gmail.com"}]
7
+ license-files = ["LICENSE"]
8
+ readme = "README.md"
9
+ requires-python = ">=3.11, < 3.14"
10
+ dependencies = [
11
+ "david8>=1.0.0b1,<2.0.0b1"
12
+ ]
13
+
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Software Development :: Libraries",
18
+ "Topic :: Database",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ ]
24
+
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/d-ganchar/david8_duckdb"
28
+ Changelog = "https://github.com/d-ganchar/david8_duckdb/releases"
29
+ Issues = "https://github.com/d-ganchar/david8_duckdb/issues"
30
+ CI = "https://github.com/d-ganchar/david8_duckdb/actions"
31
+ Documentation = "https://github.com/d-ganchar/david8_duckdb/wiki"
32
+ Source = "https://github.com/d-ganchar/david8_duckdb"
33
+
34
+
35
+ [build-system]
36
+ requires = ["setuptools>=77.0.0"]
37
+ build-backend = "setuptools.build_meta"
38
+
39
+
40
+ [tool.setuptools.packages]
41
+ find = { include = ["david8_duckdb", "david8_duckdb.*"] }
42
+
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "ruff",
47
+ "parameterized",
48
+ "pytest",
49
+ "pytest-cov>=7.0.0",
50
+ ]
51
+
52
+ [lint.select]
53
+
54
+ [lint.isort]
55
+ known-third-party = ["pytest"]
56
+ lines-after-imports = 2
57
+ lines-between-types = 1
58
+
59
+
60
+ [tool.ruff]
61
+ line-length = 120
62
+ exclude = [
63
+ ".bzr",
64
+ ".direnv",
65
+ ".eggs",
66
+ "**/.git",
67
+ "__pycache__",
68
+ "tests/fixtures/",
69
+ "benchmarks/"
70
+ ]
71
+
72
+ [tool.ruff.lint]
73
+ select = ["E", "F", "I", "UP", "N", "B", "W"]
74
+
75
+ [tool.pytest.ini_options]
76
+ testpaths = [
77
+ "tests",
78
+ "--color=yes",
79
+ ]
80
+
81
+ addopts = [
82
+
83
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ from david8.protocols.sql import QueryProtocol
2
+ from parameterized import parameterized
3
+
4
+ from tests.base_test import BaseTest
5
+
6
+
7
+ class TestCopyTo(BaseTest):
8
+ @parameterized.expand([
9
+ (
10
+ BaseTest.qb.copy_to('events', 'events.csv'),
11
+ "COPY events TO 'events.csv'",
12
+ ),
13
+ (
14
+ BaseTest.qb.copy_to('events', 'events.csv', {'DELIMITER': "','", 'APPEND': True}),
15
+ "COPY events TO 'events.csv' (DELIMITER ',', APPEND true)",
16
+ ),
17
+ # query
18
+ (
19
+ BaseTest.qb.copy_to(
20
+ BaseTest.qb.select('name', 'value').from_table('events'),
21
+ 'events.csv'
22
+ ),
23
+ "COPY (SELECT name, value FROM events) TO 'events.csv'"
24
+ ),
25
+ (
26
+ BaseTest.qb.copy_to(
27
+ BaseTest.qb.select('name', 'value').from_table('events'),
28
+ 'events.csv',
29
+ {'DELIMITER': "','", 'APPEND': True},
30
+ ),
31
+ "COPY (SELECT name, value FROM events) TO 'events.csv' (DELIMITER ',', APPEND true)",
32
+ ),
33
+ ])
34
+ def test_copy_to(self, query: QueryProtocol, exp_sql: str):
35
+ self.assertEqual(query.get_sql(), exp_sql)
@@ -0,0 +1,93 @@
1
+ from parameterized import parameterized
2
+
3
+ from david8_duckdb.protocols.sql import SelectProtocol
4
+ from tests.base_test import BaseTest
5
+
6
+
7
+ class TestSelect(BaseTest):
8
+ @parameterized.expand([
9
+ (
10
+ BaseTest.qb.select('*').from_csv('faulty.csv'),
11
+ "SELECT * FROM read_csv('faulty.csv')",
12
+ ),
13
+ (
14
+ BaseTest.qb.select('*').from_csv('flights1.csv', 'flights2.csv'),
15
+ "SELECT * FROM read_csv(['flights1.csv', 'flights2.csv'])",
16
+ ),
17
+ ])
18
+ def test_from_csv(self, query: SelectProtocol, exp_sql: str):
19
+ self.assertEqual(query.get_sql(), exp_sql)
20
+
21
+ @parameterized.expand([
22
+ (
23
+ BaseTest.qb.select('*').from_json('todos.json'),
24
+ "SELECT * FROM read_json('todos.json')",
25
+ ),
26
+ (
27
+ BaseTest.qb.select('*').from_json('todos1.json', 'todos2.json'),
28
+ "SELECT * FROM read_json(['todos1.json', 'todos2.json'])",
29
+ ),
30
+ ])
31
+ def test_from_json(self, query: SelectProtocol, exp_sql: str):
32
+ self.assertEqual(query.get_sql(), exp_sql)
33
+
34
+ @parameterized.expand([
35
+ (
36
+ BaseTest.qb.select('*').from_json_objects('todos.json'),
37
+ "SELECT * FROM read_json_objects('todos.json')",
38
+ ),
39
+ (
40
+ BaseTest.qb.select('*').from_json_objects('todos1.json', 'todos2.json'),
41
+ "SELECT * FROM read_json_objects(['todos1.json', 'todos2.json'])",
42
+ ),
43
+ ])
44
+ def test_from_json_objects(self, query: SelectProtocol, exp_sql: str):
45
+ self.assertEqual(query.get_sql(), exp_sql)
46
+
47
+ @parameterized.expand([
48
+ (
49
+ BaseTest.qb.select('*').from_ndjson_objects('todos.json'),
50
+ "SELECT * FROM read_ndjson_objects('todos.json')",
51
+ ),
52
+ (
53
+ BaseTest.qb.select('*').from_ndjson_objects('todos1.json', 'todos2.json'),
54
+ "SELECT * FROM read_ndjson_objects(['todos1.json', 'todos2.json'])",
55
+ ),
56
+ ])
57
+ def test_from_ndjson_objects(self, query: SelectProtocol, exp_sql: str):
58
+ self.assertEqual(query.get_sql(), exp_sql)
59
+
60
+ @parameterized.expand([
61
+ (
62
+ BaseTest.qb.select('*').from_json_objects_auto('todos.json'),
63
+ "SELECT * FROM read_json_objects_auto('todos.json')",
64
+ ),
65
+ (
66
+ BaseTest.qb.select('*').from_json_objects_auto('todos1.json', 'todos2.json'),
67
+ "SELECT * FROM read_json_objects_auto(['todos1.json', 'todos2.json'])",
68
+ ),
69
+ ])
70
+ def test_from_json_objects_auto(self, query: SelectProtocol, exp_sql: str):
71
+ self.assertEqual(query.get_sql(), exp_sql)
72
+
73
+ @parameterized.expand([
74
+ (
75
+ BaseTest.qb.select('*').from_parquet('file.parquet'),
76
+ "SELECT * FROM read_parquet('file.parquet')",
77
+ ),
78
+ (
79
+ BaseTest.qb.select('*').from_parquet('file1.parquet', 'file2.parquet'),
80
+ "SELECT * FROM read_parquet(['file1.parquet', 'file2.parquet'])",
81
+ ),
82
+ ])
83
+ def test_from_parquet(self, query: SelectProtocol, exp_sql: str):
84
+ self.assertEqual(query.get_sql(), exp_sql)
85
+
86
+ @parameterized.expand([
87
+ (
88
+ BaseTest.qb.select('*').from_xlsx('test.xlsx'),
89
+ "SELECT * FROM read_xlsx('test.xlsx')",
90
+ ),
91
+ ])
92
+ def test_from_xlsx(self, query: SelectProtocol, exp_sql: str):
93
+ self.assertEqual(query.get_sql(), exp_sql)