sqlcompose 0.0.1__tar.gz → 0.0.3__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,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlcompose
3
+ Version: 0.0.3
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.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=8.3.0; extra == "test"
30
+ Requires-Dist: pytest-cov>=6.1.0; extra == "test"
31
+ Dynamic: license-file
32
+
33
+ [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
34
+ [![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)
35
+ ![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)
36
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
37
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
38
+
39
+
40
+ # sqlcompose: Composition of linked SQL files
41
+ 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.
42
+
43
+ ## Examples
44
+ __Execute the script directly:__
45
+ ```console
46
+ sqlcompose query.sql
47
+ ```
48
+ ```bash
49
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
50
+ sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
51
+ ```
52
+
53
+ __Import it in another script:__
54
+ ```python
55
+ from sqlcompose import load, loads
56
+ # method 1 : loading from a file
57
+ sql1 = load("query.sql")
58
+
59
+ # method 2 : loading from an SQL string
60
+ sql2 = loads("""
61
+ select *
62
+ from dataset.table main
63
+ inner join $INCLUDE(other.sql) other
64
+ on other.field = main.field
65
+ """)
66
+ ```
67
+
68
+ ## Preparing SQL scripts
69
+ 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.
70
+
71
+ ```sql
72
+ --main-query.sql
73
+ select * from $INCLUDE(includes\included-query2.sql)
74
+ ```
75
+ ```sql
76
+ --included-query1.sql
77
+ select 1 as test
78
+ ```
79
+ ```sql
80
+ --included-query2.sql
81
+ select * from $INCLUDE(included-query1.sql)
82
+ union all
83
+ select * from $INCLUDE(nested\included-query3.sql)
84
+ ```
85
+ ```sql
86
+ --nested\included-query3.sql
87
+ select 1 as test
88
+ ```
89
+ Which outputs:
90
+ ```sql
91
+ WITH Q_1_1 AS (
92
+ WITH Q_2_1 AS (
93
+ --includes\included-query1.sql
94
+ select 1 as test
95
+ ), Q_2_2 AS (
96
+ --includes\nested\included-query3.sql
97
+ select 1 as test
98
+ ), Q_2 AS (
99
+ --includes\included-query2.sql
100
+ select * from Q_2_1
101
+ union all
102
+ select * from Q_2_2
103
+ )
104
+ SELECT * FROM Q_2
105
+ ), Q_1 AS (
106
+ --test\main-query.sql
107
+ select * from Q_1_1
108
+ )
109
+ SELECT * FROM Q_1
110
+ ```
@@ -1,20 +1,9 @@
1
- Metadata-Version: 2.3
2
- Name: sqlcompose
3
- Version: 0.0.1
4
- Summary: Composition of linked SQL files
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.
@@ -24,7 +13,7 @@ __Execute the script directly:__
24
13
  ```console
25
14
  sqlcompose query.sql
26
15
  ```
27
- ```console
16
+ ```bash
28
17
  sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
29
18
  sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
30
19
  ```
@@ -87,4 +76,3 @@ WITH Q_1_1 AS (
87
76
  )
88
77
  SELECT * FROM Q_1
89
78
  ```
90
-
@@ -0,0 +1,49 @@
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.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Software Development :: Libraries",
27
+ "Typing :: Typed"
28
+ ]
29
+ dependencies = []
30
+ requires-python = ">=3.10"
31
+
32
+ [project.urls]
33
+ repository = "https://github.com/apmadsen/sqlcompose"
34
+
35
+ [project.scripts]
36
+ "sqlcompose" = "sqlcompose.__main__:main"
37
+
38
+ [project.optional-dependencies]
39
+ test = [
40
+ "pytest>=8.3.0",
41
+ "pytest-cov>=6.1.0",
42
+ ]
43
+
44
+ [tool.setuptools.dynamic]
45
+ version = { attr = 'sqlcompose.__init__.__version__' }
46
+
47
+ [build-system]
48
+ requires = ["setuptools >= 77.0.3"]
49
+ 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.3"
@@ -0,0 +1,15 @@
1
+ from sys import argv, exit, stderr, stdout
2
+ from sqlcompose.core.app import app
3
+
4
+ def main(): # pragma: no cover
5
+ result, code = app(argv[1:])
6
+
7
+ if code == 0:
8
+ stdout.write(result)
9
+ else:
10
+ stderr.write(result)
11
+
12
+ exit(code)
13
+
14
+ if __name__ == "__main__":
15
+ main()
@@ -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.copy()
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,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlcompose
3
+ Version: 0.0.3
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.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=8.3.0; extra == "test"
30
+ Requires-Dist: pytest-cov>=6.1.0; extra == "test"
31
+ Dynamic: license-file
32
+
33
+ [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
34
+ [![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)
35
+ ![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)
36
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
37
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
38
+
39
+
40
+ # sqlcompose: Composition of linked SQL files
41
+ 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.
42
+
43
+ ## Examples
44
+ __Execute the script directly:__
45
+ ```console
46
+ sqlcompose query.sql
47
+ ```
48
+ ```bash
49
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
50
+ sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
51
+ ```
52
+
53
+ __Import it in another script:__
54
+ ```python
55
+ from sqlcompose import load, loads
56
+ # method 1 : loading from a file
57
+ sql1 = load("query.sql")
58
+
59
+ # method 2 : loading from an SQL string
60
+ sql2 = loads("""
61
+ select *
62
+ from dataset.table main
63
+ inner join $INCLUDE(other.sql) other
64
+ on other.field = main.field
65
+ """)
66
+ ```
67
+
68
+ ## Preparing SQL scripts
69
+ 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.
70
+
71
+ ```sql
72
+ --main-query.sql
73
+ select * from $INCLUDE(includes\included-query2.sql)
74
+ ```
75
+ ```sql
76
+ --included-query1.sql
77
+ select 1 as test
78
+ ```
79
+ ```sql
80
+ --included-query2.sql
81
+ select * from $INCLUDE(included-query1.sql)
82
+ union all
83
+ select * from $INCLUDE(nested\included-query3.sql)
84
+ ```
85
+ ```sql
86
+ --nested\included-query3.sql
87
+ select 1 as test
88
+ ```
89
+ Which outputs:
90
+ ```sql
91
+ WITH Q_1_1 AS (
92
+ WITH Q_2_1 AS (
93
+ --includes\included-query1.sql
94
+ select 1 as test
95
+ ), Q_2_2 AS (
96
+ --includes\nested\included-query3.sql
97
+ select 1 as test
98
+ ), Q_2 AS (
99
+ --includes\included-query2.sql
100
+ select * from Q_2_1
101
+ union all
102
+ select * from Q_2_2
103
+ )
104
+ SELECT * FROM Q_2
105
+ ), Q_1 AS (
106
+ --test\main-query.sql
107
+ select * from Q_1_1
108
+ )
109
+ SELECT * FROM Q_1
110
+ ```
@@ -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,71 +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
- ```console
7
- sqlcompose query.sql
8
- ```
9
- ```console
10
- sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
11
- sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
12
- ```
13
-
14
- __Import it in another script:__
15
- ```python
16
- from sqlcompose import load, loads
17
- # method 1 : loading from a file
18
- sql1 = load("query.sql")
19
-
20
- # method 2 : loading from an SQL string
21
- sql2 = loads("""
22
- select *
23
- from dataset.table main
24
- inner join $INCLUDE(other.sql) other
25
- on other.field = main.field
26
- """)
27
- ```
28
-
29
- ## Preparing SQL scripts
30
- 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.
31
-
32
- ```sql
33
- --main-query.sql
34
- select * from $INCLUDE(includes\included-query2.sql)
35
- ```
36
- ```sql
37
- --included-query1.sql
38
- select 1 as test
39
- ```
40
- ```sql
41
- --included-query2.sql
42
- select * from $INCLUDE(included-query1.sql)
43
- union all
44
- select * from $INCLUDE(nested\included-query3.sql)
45
- ```
46
- ```sql
47
- --nested\included-query3.sql
48
- select 1 as test
49
- ```
50
- Which outputs:
51
- ```sql
52
- WITH Q_1_1 AS (
53
- WITH Q_2_1 AS (
54
- --includes\included-query1.sql
55
- select 1 as test
56
- ), Q_2_2 AS (
57
- --includes\nested\included-query3.sql
58
- select 1 as test
59
- ), Q_2 AS (
60
- --includes\included-query2.sql
61
- select * from Q_2_1
62
- union all
63
- select * from Q_2_2
64
- )
65
- SELECT * FROM Q_2
66
- ), Q_1 AS (
67
- --test\main-query.sql
68
- select * from Q_1_1
69
- )
70
- SELECT * FROM Q_1
71
- ```
@@ -1,24 +0,0 @@
1
- [tool.poetry]
2
- name = "sqlcompose"
3
- version = "0.0.1"
4
- description = "Composition of linked SQL files"
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