sqlcompose 0.0.0__tar.gz → 0.0.2__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,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlcompose
3
+ Version: 0.0.2
4
+ Summary: Composition of linked SQL files
5
+ Author-email: Anders Madsen <anders.madsen@alphavue.com>
6
+ License: MIT
7
+ Project-URL: repository, https://github.com/apmadsen/sqlcompose
8
+ Keywords: sql,composition,windows,linux
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Development Status :: 6 - Mature
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Software Development :: Libraries
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=8.3.0; extra == "test"
31
+ Requires-Dist: pytest-cov>=6.1.0; extra == "test"
32
+ Dynamic: license-file
33
+
34
+ [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
35
+ [![Coverage](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
36
+ ![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)
37
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
38
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
39
+
40
+
41
+ # sqlcompose: Composition of linked SQL files
42
+ sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
43
+
44
+ ## Examples
45
+ __Execute the script directly:__
46
+ ```console
47
+ sqlcompose query.sql
48
+ ```
49
+ ```bash
50
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
51
+ sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
52
+ ```
53
+
54
+ __Import it in another script:__
55
+ ```python
56
+ from sqlcompose import load, loads
57
+ # method 1 : loading from a file
58
+ sql1 = load("query.sql")
59
+
60
+ # method 2 : loading from an SQL string
61
+ sql2 = loads("""
62
+ select *
63
+ from dataset.table main
64
+ inner join $INCLUDE(other.sql) other
65
+ on other.field = main.field
66
+ """)
67
+ ```
68
+
69
+ ## Preparing SQL scripts
70
+ Insert a `$INCLUDE(filename)` where the reference to the file should be in the resulting SQL, keeping in mind that references are loaded relative to the file loaded or the current working dir in case of an SQL string.
71
+
72
+ ```sql
73
+ --main-query.sql
74
+ select * from $INCLUDE(includes\included-query2.sql)
75
+ ```
76
+ ```sql
77
+ --included-query1.sql
78
+ select 1 as test
79
+ ```
80
+ ```sql
81
+ --included-query2.sql
82
+ select * from $INCLUDE(included-query1.sql)
83
+ union all
84
+ select * from $INCLUDE(nested\included-query3.sql)
85
+ ```
86
+ ```sql
87
+ --nested\included-query3.sql
88
+ select 1 as test
89
+ ```
90
+ Which outputs:
91
+ ```sql
92
+ WITH Q_1_1 AS (
93
+ WITH Q_2_1 AS (
94
+ --includes\included-query1.sql
95
+ select 1 as test
96
+ ), Q_2_2 AS (
97
+ --includes\nested\included-query3.sql
98
+ select 1 as test
99
+ ), Q_2 AS (
100
+ --includes\included-query2.sql
101
+ select * from Q_2_1
102
+ union all
103
+ select * from Q_2_2
104
+ )
105
+ SELECT * FROM Q_2
106
+ ), Q_1 AS (
107
+ --test\main-query.sql
108
+ select * from Q_1_1
109
+ )
110
+ SELECT * FROM Q_1
111
+ ```
@@ -1,29 +1,21 @@
1
- Metadata-Version: 2.3
2
- Name: sqlcompose
3
- Version: 0.0.0
4
- Summary:
5
- License: MIT
6
- Author: Anders Madsen
7
- Author-email: anders.madsen@alphavue.com
8
- Requires-Python: >=3.10,<=3.13
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Project-URL: Repository, https://github.com/apmadsen/sqlcompose
17
- Description-Content-Type: text/markdown
1
+ [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
2
+ [![Coverage](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
3
+ ![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)
4
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
5
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
6
+
18
7
 
19
8
  # sqlcompose: Composition of linked SQL files
20
9
  sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
21
10
 
22
11
  ## Examples
23
12
  __Execute the script directly:__
24
- ```
13
+ ```console
25
14
  sqlcompose query.sql
26
- sqlcompose "select * from $INCLUDE(included-query1.sql)"
15
+ ```
16
+ ```bash
17
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
18
+ sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
27
19
  ```
28
20
 
29
21
  __Import it in another script:__
@@ -84,4 +76,3 @@ WITH Q_1_1 AS (
84
76
  )
85
77
  SELECT * FROM Q_1
86
78
  ```
87
-
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "sqlcompose"
3
+ dynamic = ["version"]
4
+ description = "Composition of linked SQL files"
5
+ keywords = ["sql", "composition", "windows", "linux"]
6
+ readme = "README.md"
7
+ authors = [
8
+ { name = "Anders Madsen", email = "anders.madsen@alphavue.com" }
9
+ ]
10
+ license = { text = "MIT" }
11
+ classifiers = [
12
+ "Development Status :: 5 - Production/Stable",
13
+ "Development Status :: 6 - Mature",
14
+ "Operating System :: Microsoft :: Windows",
15
+ "Operating System :: POSIX :: Linux",
16
+ "Intended Audience :: Developers",
17
+ "Natural Language :: English",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Software Development :: Libraries",
28
+ "Typing :: Typed"
29
+ ]
30
+ dependencies = []
31
+ requires-python = ">=3.10"
32
+
33
+ [project.urls]
34
+ repository = "https://github.com/apmadsen/sqlcompose"
35
+
36
+ [project.scripts]
37
+ "sqlcompose" = "sqlcompose.main:main"
38
+
39
+ [project.optional-dependencies]
40
+ test = [
41
+ "pytest>=8.3.0",
42
+ "pytest-cov>=6.1.0",
43
+ ]
44
+
45
+ [tool.setuptools.dynamic]
46
+ version = { attr = 'sqlcompose.__init__.__version__' }
47
+
48
+ [build-system]
49
+ requires = ["setuptools >= 77.0.3"]
50
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ from sqlcompose.core.functions import load, loads
2
+ from sqlcompose.core.circular_dependency_error import CircularDependencyError
3
+
4
+ __all__ = [
5
+ 'load',
6
+ 'loads',
7
+ 'CircularDependencyError',
8
+ ]
9
+ __package_name__ = "sqlcompose"
10
+ __version__ = "0.0.2"
@@ -0,0 +1,13 @@
1
+ from sys import argv, exit, stderr, stdout
2
+ from sqlcompose.core.app import app
3
+
4
+
5
+ if __name__ == "__main__":
6
+ result, code = app(argv[1:])
7
+
8
+ if code == 0:
9
+ stdout.write(result)
10
+ else:
11
+ stderr.write(result)
12
+
13
+ exit(code)
@@ -1,30 +1,22 @@
1
1
  from os import path
2
- from sys import exit
3
2
  from argparse import ArgumentParser
4
3
 
5
4
  from sqlcompose.core.functions import load, loads
6
5
 
7
6
 
8
- def main():
7
+ def app(args: list[str]) -> tuple[str, int ]:
9
8
  try:
10
9
  parser = ArgumentParser(prog = "sqlcompose")
11
10
  parser.add_argument("input", type=str, help = "SQL expression or location of an SQL file")
12
- pargs = parser.parse_args()
11
+ pargs = parser.parse_args(args)
13
12
 
14
13
  if path.isfile(pargs.input):
15
14
  sql = load(pargs.input)
16
15
  else:
17
16
  sql = loads(pargs.input)
18
17
 
19
- print(sql)
20
-
21
- return
22
- except SystemExit:
23
- raise # let argparse print error(s)
24
- except Exception as ex:
25
- print(f"Unexpected error: {ex}")
26
- exit(1)
27
-
28
-
29
- if __name__ == "__main__":
30
- main()
18
+ return sql, 0
19
+ except SystemExit as ex:
20
+ return str(ex), 2
21
+ except Exception as ex: # pragma: no cover
22
+ return f"Unexpected error: {ex}", 1
@@ -0,0 +1,2 @@
1
+ class CircularDependencyError(Exception):
2
+ ...
@@ -1,9 +1,10 @@
1
1
  from os import path
2
- from typing import Sequence, MutableSequence, MutableMapping, cast
2
+ from typing import Sequence
3
3
  from os import path
4
4
  from re import compile, sub, escape, IGNORECASE
5
5
  from textwrap import indent
6
6
 
7
+ from sqlcompose.core.circular_dependency_error import CircularDependencyError
7
8
  from sqlcompose.core.include import Include
8
9
  from sqlcompose.core.compat import fix_path, get_relative_path
9
10
 
@@ -19,7 +20,7 @@ def loads(sql: str) -> str:
19
20
  Returns:
20
21
  str: The composed query
21
22
  """
22
- return cast(str, compose(sql, "SQL", path.curdir, path.curdir))
23
+ return compose(sql, "SQL", path.curdir, path.curdir)
23
24
 
24
25
 
25
26
  def load(filename: str) -> str:
@@ -40,7 +41,7 @@ def load(filename: str) -> str:
40
41
 
41
42
 
42
43
  with open(filename, "r", encoding="utf-8") as file:
43
- return cast(str, compose(file.read(), filename, filename, path.dirname(filename)))
44
+ return compose(file.read(), filename, filename, path.dirname(filename))
44
45
 
45
46
 
46
47
  def compose(
@@ -49,52 +50,55 @@ def compose(
49
50
  file_path: str,
50
51
  root: str,
51
52
  level: int = 1,
52
- included: MutableSequence[str] | None = None,
53
- included1: MutableMapping[str, tuple[str, int]] | None = None
54
- ) -> str | None:
53
+ stack: list[str] | None = None
54
+ ) -> str:
55
55
 
56
56
  file_path = fix_path(file_path)
57
- included = included or []
58
- included1 = included1 or {}
57
+ stack = stack or []
58
+ parent = stack[-1] if stack else None
59
+ name = get_relative_path(name, root)
60
+ index = 1
61
+ included: list[str] = []
59
62
  includes: list[Include] = []
60
- parent = included[len(included)-2] if len(included) > 1 else None
63
+ stack.append(name)
61
64
 
62
- if name in included and included1[name][1] == level:
63
- #duplicate include at the same level - return none, to use the previously composed SQL
64
- return None
65
- elif name in included:
66
- raise Exception(f"Circular dependency detected: File \"{get_relative_path(file_path, root)}\" has already been already included")
67
- else:
68
- included.append(name)
69
- included1[name] = (file_path, level)
70
-
71
- index = 1
65
+ if len(stack) > 1 and name in (stack[1:-1]):
66
+ raise CircularDependencyError
72
67
 
73
68
  for match in REGEX_INCLUDE.finditer(sql):
74
69
  file_path_inner = fix_path(path.join(path.dirname(file_path), match.group(1)))
75
- try:
76
- with open(file_path_inner, "r", encoding="utf-8") as file:
77
- composed = compose(file.read(), match.group(1), file_path_inner, root, level=level+1)
78
- except FileNotFoundError:
79
- if not parent is None:
80
- raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" which was referred to in \"{get_relative_path(parent, root)}\", was not found...")
81
- else:
82
- raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" was not found...")
83
-
84
- if composed is not None:
85
- includes.append(
86
- Include(
87
- composed,
88
- f"Q_{level}_{index}",
89
- match.group(0),
90
- match.group(1)
70
+ if file_path_inner not in included:
71
+ included.append(file_path_inner)
72
+ try:
73
+ with open(file_path_inner, "r", encoding="utf-8") as file:
74
+ composed = compose(
75
+ file.read(),
76
+ match.group(1),
77
+ file_path_inner,
78
+ root,
79
+ level + 1,
80
+ stack
81
+ )
82
+ includes.append(
83
+ Include(
84
+ composed,
85
+ f"Q_{level}_{index}",
86
+ match.group(0),
87
+ match.group(1)
88
+ )
91
89
  )
92
- )
93
- index = index + 1
90
+ index = index + 1
91
+ except FileNotFoundError:
92
+ if parent is not None:
93
+ raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" which was referred to in \"{get_relative_path(parent, root)}\", was not found...")
94
+ else:
95
+ raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" was not found...")
94
96
 
95
97
  for include in includes:
96
98
  sql = sub(escape(include.match), include.name, sql)
97
99
 
100
+ stack.pop()
101
+
98
102
  return wrap_cte_sql(includes, sql, level, name)
99
103
 
100
104
  def wrap_cte_sql(includes: Sequence[Include], sql: str, level: int, source: str) -> str:
@@ -20,5 +20,5 @@ class Include():
20
20
  return self.__match
21
21
 
22
22
  @property
23
- def source(self) -> str:
23
+ def source(self) -> str: # pragma: no cover
24
24
  return self.__source
File without changes
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlcompose
3
+ Version: 0.0.2
4
+ Summary: Composition of linked SQL files
5
+ Author-email: Anders Madsen <anders.madsen@alphavue.com>
6
+ License: MIT
7
+ Project-URL: repository, https://github.com/apmadsen/sqlcompose
8
+ Keywords: sql,composition,windows,linux
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Development Status :: 6 - Mature
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Software Development :: Libraries
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=8.3.0; extra == "test"
31
+ Requires-Dist: pytest-cov>=6.1.0; extra == "test"
32
+ Dynamic: license-file
33
+
34
+ [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
35
+ [![Coverage](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
36
+ ![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)
37
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
38
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
39
+
40
+
41
+ # sqlcompose: Composition of linked SQL files
42
+ sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
43
+
44
+ ## Examples
45
+ __Execute the script directly:__
46
+ ```console
47
+ sqlcompose query.sql
48
+ ```
49
+ ```bash
50
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
51
+ sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
52
+ ```
53
+
54
+ __Import it in another script:__
55
+ ```python
56
+ from sqlcompose import load, loads
57
+ # method 1 : loading from a file
58
+ sql1 = load("query.sql")
59
+
60
+ # method 2 : loading from an SQL string
61
+ sql2 = loads("""
62
+ select *
63
+ from dataset.table main
64
+ inner join $INCLUDE(other.sql) other
65
+ on other.field = main.field
66
+ """)
67
+ ```
68
+
69
+ ## Preparing SQL scripts
70
+ Insert a `$INCLUDE(filename)` where the reference to the file should be in the resulting SQL, keeping in mind that references are loaded relative to the file loaded or the current working dir in case of an SQL string.
71
+
72
+ ```sql
73
+ --main-query.sql
74
+ select * from $INCLUDE(includes\included-query2.sql)
75
+ ```
76
+ ```sql
77
+ --included-query1.sql
78
+ select 1 as test
79
+ ```
80
+ ```sql
81
+ --included-query2.sql
82
+ select * from $INCLUDE(included-query1.sql)
83
+ union all
84
+ select * from $INCLUDE(nested\included-query3.sql)
85
+ ```
86
+ ```sql
87
+ --nested\included-query3.sql
88
+ select 1 as test
89
+ ```
90
+ Which outputs:
91
+ ```sql
92
+ WITH Q_1_1 AS (
93
+ WITH Q_2_1 AS (
94
+ --includes\included-query1.sql
95
+ select 1 as test
96
+ ), Q_2_2 AS (
97
+ --includes\nested\included-query3.sql
98
+ select 1 as test
99
+ ), Q_2 AS (
100
+ --includes\included-query2.sql
101
+ select * from Q_2_1
102
+ union all
103
+ select * from Q_2_2
104
+ )
105
+ SELECT * FROM Q_2
106
+ ), Q_1 AS (
107
+ --test\main-query.sql
108
+ select * from Q_1_1
109
+ )
110
+ SELECT * FROM Q_1
111
+ ```
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/sqlcompose/__init__.py
5
+ src/sqlcompose/__main__.py
6
+ src/sqlcompose/py.typed
7
+ src/sqlcompose.egg-info/PKG-INFO
8
+ src/sqlcompose.egg-info/SOURCES.txt
9
+ src/sqlcompose.egg-info/dependency_links.txt
10
+ src/sqlcompose.egg-info/entry_points.txt
11
+ src/sqlcompose.egg-info/requires.txt
12
+ src/sqlcompose.egg-info/top_level.txt
13
+ src/sqlcompose/core/app.py
14
+ src/sqlcompose/core/circular_dependency_error.py
15
+ src/sqlcompose/core/compat.py
16
+ src/sqlcompose/core/functions.py
17
+ src/sqlcompose/core/include.py
18
+ tests/test_app.py
19
+ tests/test_compat.py
20
+ tests/test_functions.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sqlcompose = sqlcompose.main:main
@@ -0,0 +1,4 @@
1
+
2
+ [test]
3
+ pytest>=8.3.0
4
+ pytest-cov>=6.1.0
@@ -0,0 +1 @@
1
+ sqlcompose
@@ -0,0 +1,29 @@
1
+ # pyright: basic
2
+ from unittest import TestCase, main
3
+
4
+ from sqlcompose.core.app import app
5
+
6
+ class Test(TestCase):
7
+ # def test_nonexisting(self):
8
+ # _result, code = app(["nonexisting.sql"])
9
+ # self.assertEqual(code, 1)
10
+
11
+ def test_missing_args(self):
12
+ _result, code = app([])
13
+ self.assertEqual(code, 2)
14
+
15
+ def test_existing_file_by_path(self):
16
+ result, code = app(["tests/main-query.sql"])
17
+ self.assertGreater(len(result), 0)
18
+ self.assertEqual(code, 0)
19
+
20
+ def test_sql(self):
21
+ result, code = app(["SELECT * FROM $INCLUDE(tests/main-query.sql)"])
22
+ # print(result)
23
+ self.assertGreater(len(result), 0)
24
+ self.assertEqual(code, 0)
25
+
26
+
27
+ if __name__ == '__main__':
28
+ main()
29
+
@@ -0,0 +1,52 @@
1
+ # pyright: basic
2
+ from unittest import TestCase, main
3
+ from sys import platform
4
+
5
+ from sqlcompose.core.compat import fix_path, get_relative_path
6
+
7
+ class Test(TestCase):
8
+
9
+ def test_fix_path(self):
10
+ if platform == "win32":
11
+ tests = {
12
+ "sql\\file.sql" : "sql\\file.sql",
13
+ "sql\\file.sql" : "sql/file.sql",
14
+ }
15
+ else:
16
+ tests = {
17
+ "sql/file.sql" : "sql\\file.sql",
18
+ "sql/file.sql" : "sql/file.sql",
19
+ }
20
+
21
+ for expected_result, file_path in tests.items():
22
+ result = fix_path(file_path)
23
+ self.assertEqual(result, expected_result)
24
+
25
+
26
+ def test_get_relative_path(self):
27
+ if platform == "win32":
28
+ tests = {
29
+ "sql\\file.sql" : ("sql\\file.sql", "sql\\file.sql"),
30
+ "sql\\file.sql" : ("sql\\file.sql", "c:\\app\\"),
31
+ "sql\\file.sql" : ("c:\\app\\sql\\file.sql", "c:\\app\\"),
32
+ }
33
+
34
+ p = "sql\\file.sql"
35
+ self.assertEqual(p, get_relative_path(p, p))
36
+ else:
37
+ tests = {
38
+ "sql/file.sql" : ("sql/file.sql", "sql/file.sql"),
39
+ "sql/file.sql" : ("sql/file.sql", "/app/"),
40
+ "sql/file.sql" : ("/app/sql/file.sql", "/app/"),
41
+ }
42
+ p = "sql/file.sql"
43
+ self.assertEqual(p, get_relative_path(p, p))
44
+
45
+ for expected_result, (file_path, root) in tests.items():
46
+ result = get_relative_path(file_path, root)
47
+ self.assertEqual(result, expected_result)
48
+
49
+
50
+ if __name__ == '__main__':
51
+ main()
52
+
@@ -0,0 +1,40 @@
1
+ # pyright: basic
2
+ from unittest import TestCase, main
3
+
4
+ from sqlcompose import loads, load, CircularDependencyError
5
+ from sqlcompose.core.functions import compose
6
+
7
+ class Test(TestCase):
8
+ def test_nonexisting(self):
9
+ self.assertRaises(FileNotFoundError, load, "nonexisting.sql")
10
+ self.assertRaises(FileNotFoundError, load, "tests/non_existing_include.sql")
11
+ self.assertRaises(FileNotFoundError, compose, "select * from $INCLUDE(some_file_that_does_not_exist.sql)", "SQL", ".", ".")
12
+
13
+ filename = "tests/existing_include.sql"
14
+ with open(filename, "r", encoding="utf-8") as file:
15
+ self.assertRaises(FileNotFoundError, compose, file.read(), filename, filename, ".")
16
+
17
+ def test_reuse_composition(self):
18
+ result = compose("select * from $INCLUDE(tests/includes/included-query3.sql)", "SQL", ".", ".")
19
+ # print(result)
20
+ self.assertTrue(len(result) > 0)
21
+
22
+
23
+ def test_existing_file_by_path(self):
24
+ result = load("tests/main-query.sql")
25
+ # print(result)
26
+ self.assertTrue(len(result) > 0)
27
+
28
+ def test_sql(self):
29
+ result = loads("SELECT * FROM $INCLUDE(tests/main-query.sql)")
30
+ # print(result)
31
+ self.assertTrue(len(result) > 0)
32
+
33
+ def test_circular_dependency(self):
34
+ self.assertRaises(CircularDependencyError, load, "tests/circular_left.sql")
35
+
36
+
37
+
38
+ if __name__ == '__main__':
39
+ main()
40
+
@@ -1,68 +0,0 @@
1
- # sqlcompose: Composition of linked SQL files
2
- sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
3
-
4
- ## Examples
5
- __Execute the script directly:__
6
- ```
7
- sqlcompose query.sql
8
- sqlcompose "select * from $INCLUDE(included-query1.sql)"
9
- ```
10
-
11
- __Import it in another script:__
12
- ```python
13
- from sqlcompose import load, loads
14
- # method 1 : loading from a file
15
- sql1 = load("query.sql")
16
-
17
- # method 2 : loading from an SQL string
18
- sql2 = loads("""
19
- select *
20
- from dataset.table main
21
- inner join $INCLUDE(other.sql) other
22
- on other.field = main.field
23
- """)
24
- ```
25
-
26
- ## Preparing SQL scripts
27
- Insert a `$INCLUDE(filename)` where the reference to the file should be in the resulting SQL, keeping in mind that references are loaded relative to the file loaded or the current working dir in case of an SQL string.
28
-
29
- ```sql
30
- --main-query.sql
31
- select * from $INCLUDE(includes\included-query2.sql)
32
- ```
33
- ```sql
34
- --included-query1.sql
35
- select 1 as test
36
- ```
37
- ```sql
38
- --included-query2.sql
39
- select * from $INCLUDE(included-query1.sql)
40
- union all
41
- select * from $INCLUDE(nested\included-query3.sql)
42
- ```
43
- ```sql
44
- --nested\included-query3.sql
45
- select 1 as test
46
- ```
47
- Which outputs:
48
- ```sql
49
- WITH Q_1_1 AS (
50
- WITH Q_2_1 AS (
51
- --includes\included-query1.sql
52
- select 1 as test
53
- ), Q_2_2 AS (
54
- --includes\nested\included-query3.sql
55
- select 1 as test
56
- ), Q_2 AS (
57
- --includes\included-query2.sql
58
- select * from Q_2_1
59
- union all
60
- select * from Q_2_2
61
- )
62
- SELECT * FROM Q_2
63
- ), Q_1 AS (
64
- --test\main-query.sql
65
- select * from Q_1_1
66
- )
67
- SELECT * FROM Q_1
68
- ```
@@ -1,24 +0,0 @@
1
- [tool.poetry]
2
- name = "sqlcompose"
3
- version = "0.0.0"
4
- description = ""
5
- readme = "README.md"
6
- authors = ["Anders Madsen <anders.madsen@alphavue.com>"]
7
- license = "MIT"
8
- repository = "https://github.com/apmadsen/sqlcompose"
9
- classifiers = [
10
- "Operating System :: OS Independent"
11
- ]
12
- packages = [
13
- { include = "sqlcompose", from = "src" }
14
- ]
15
-
16
- [tool.poetry.scripts]
17
- "sqlcompose" = "sqlcompose.main:main"
18
-
19
- [tool.poetry.dependencies]
20
- python = ">=3.10,<=3.13"
21
-
22
- [build-system]
23
- requires = ["poetry-core>=1.0.0"]
24
- build-backend = "poetry.core.masonry.api"
@@ -1,6 +0,0 @@
1
- from sqlcompose.core.functions import load, loads
2
-
3
- __all__ = [
4
- 'load',
5
- 'loads',
6
- ]
File without changes