sqlcompose 0.0.0__py3-none-any.whl → 0.0.2__py3-none-any.whl

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.
sqlcompose/__init__.py CHANGED
@@ -1,6 +1,10 @@
1
1
  from sqlcompose.core.functions import load, loads
2
+ from sqlcompose.core.circular_dependency_error import CircularDependencyError
2
3
 
3
4
  __all__ = [
4
5
  'load',
5
6
  'loads',
6
- ]
7
+ 'CircularDependencyError',
8
+ ]
9
+ __package_name__ = "sqlcompose"
10
+ __version__ = "0.0.2"
sqlcompose/__main__.py CHANGED
@@ -1,30 +1,13 @@
1
- from os import path
2
- from sys import exit
3
- from argparse import ArgumentParser
1
+ from sys import argv, exit, stderr, stdout
2
+ from sqlcompose.core.app import app
4
3
 
5
- from sqlcompose.core.functions import load, loads
6
4
 
5
+ if __name__ == "__main__":
6
+ result, code = app(argv[1:])
7
7
 
8
- def main():
9
- try:
10
- parser = ArgumentParser(prog = "sqlcompose")
11
- parser.add_argument("input", type=str, help = "SQL expression or location of an SQL file")
12
- pargs = parser.parse_args()
13
-
14
- if path.isfile(pargs.input):
15
- sql = load(pargs.input)
16
- else:
17
- sql = loads(pargs.input)
18
-
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
-
8
+ if code == 0:
9
+ stdout.write(result)
10
+ else:
11
+ stderr.write(result)
28
12
 
29
- if __name__ == "__main__":
30
- main()
13
+ exit(code)
sqlcompose/core/app.py ADDED
@@ -0,0 +1,22 @@
1
+ from os import path
2
+ from argparse import ArgumentParser
3
+
4
+ from sqlcompose.core.functions import load, loads
5
+
6
+
7
+ def app(args: list[str]) -> tuple[str, int ]:
8
+ try:
9
+ parser = ArgumentParser(prog = "sqlcompose")
10
+ parser.add_argument("input", type=str, help = "SQL expression or location of an SQL file")
11
+ pargs = parser.parse_args(args)
12
+
13
+ if path.isfile(pargs.input):
14
+ sql = load(pargs.input)
15
+ else:
16
+ sql = loads(pargs.input)
17
+
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
sqlcompose/py.typed ADDED
File without changes
@@ -1,29 +1,54 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: sqlcompose
3
- Version: 0.0.0
4
- Summary:
3
+ Version: 0.0.2
4
+ Summary: Composition of linked SQL files
5
+ Author-email: Anders Madsen <anders.madsen@alphavue.com>
5
6
  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
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
11
16
  Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
12
18
  Classifier: Programming Language :: Python :: 3.10
13
19
  Classifier: Programming Language :: Python :: 3.11
14
20
  Classifier: Programming Language :: Python :: 3.12
15
21
  Classifier: Programming Language :: Python :: 3.13
16
- Project-URL: Repository, https://github.com/apmadsen/sqlcompose
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
17
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
+
18
40
 
19
41
  # sqlcompose: Composition of linked SQL files
20
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.
21
43
 
22
44
  ## Examples
23
45
  __Execute the script directly:__
24
- ```
46
+ ```console
25
47
  sqlcompose query.sql
26
- sqlcompose "select * from $INCLUDE(included-query1.sql)"
48
+ ```
49
+ ```bash
50
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
51
+ sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
27
52
  ```
28
53
 
29
54
  __Import it in another script:__
@@ -84,4 +109,3 @@ WITH Q_1_1 AS (
84
109
  )
85
110
  SELECT * FROM Q_1
86
111
  ```
87
-
@@ -0,0 +1,14 @@
1
+ sqlcompose/__init__.py,sha256=Qh82ZdC3xjE6-a-BnwViHMgXRGuIeO3Sdp0SMKv5Ku4,252
2
+ sqlcompose/__main__.py,sha256=8-S7mIkyEbI2lYLhB_spSa6EAOHdSIkZqoQuFA2UNIM,244
3
+ sqlcompose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ sqlcompose/core/app.py,sha256=3QstrdKsuTd59xtciwU9bVE8ndwC9V2wu4H_tgZU2DU,650
5
+ sqlcompose/core/circular_dependency_error.py,sha256=3JqXYqX4Qg5QC4te2IuiqU2snC2oU--g9m1qFah4A38,49
6
+ sqlcompose/core/compat.py,sha256=pIF8Lk-5MhwAlHD4_RCMXYOxiDyTJIA5nlbR5_Dyw0g,819
7
+ sqlcompose/core/functions.py,sha256=13960AR1GRJsoUwfI6g53opPYEcgoCdzVE7W2mcMbOw,3622
8
+ sqlcompose/core/include.py,sha256=0k5hyO0U_cYbj8iByIQoOF_YQb3kdRgegZVsYiMU2Vg,560
9
+ sqlcompose-0.0.2.dist-info/licenses/LICENSE,sha256=WtJk2ScYuo_lEP42z3DYU191mO2oU8z00nPsL9KAWCQ,1063
10
+ sqlcompose-0.0.2.dist-info/METADATA,sha256=F_iENCopEl7xrVlVBzCBPmirfWO78vcPB_BQyEuskx4,3756
11
+ sqlcompose-0.0.2.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
12
+ sqlcompose-0.0.2.dist-info/entry_points.txt,sha256=8-odh3ht86pH5a3UaGM0CNwyVT4upBILl6HU8SVPLPs,52
13
+ sqlcompose-0.0.2.dist-info/top_level.txt,sha256=-8g6qpTAzLcUSsbk3v6mgNbY34pJ2LIplugBVYB5Jq0,11
14
+ sqlcompose-0.0.2.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: setuptools (80.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sqlcompose = sqlcompose.main:main
@@ -0,0 +1 @@
1
+ sqlcompose
@@ -1,10 +0,0 @@
1
- sqlcompose/__init__.py,sha256=yAFSzkLv3bCZ6e4OWvws6aYjwHfLQcxhGVzK12Fs_CQ,89
2
- sqlcompose/__main__.py,sha256=5TpsgP8V1SC14siCyUcHdEYWm9vDwxaUtqUgCC5f_4w,691
3
- sqlcompose/core/compat.py,sha256=pIF8Lk-5MhwAlHD4_RCMXYOxiDyTJIA5nlbR5_Dyw0g,819
4
- sqlcompose/core/functions.py,sha256=eHK_STVYOGDn3dtShTa0xaasYQi220tF12bQH9qEEUI,3693
5
- sqlcompose/core/include.py,sha256=ejNaz2p2Lq1pUnwMxtBqE4p_hBOtG8OynqRpECLEbNQ,541
6
- sqlcompose-0.0.0.dist-info/LICENSE,sha256=WtJk2ScYuo_lEP42z3DYU191mO2oU8z00nPsL9KAWCQ,1063
7
- sqlcompose-0.0.0.dist-info/METADATA,sha256=dPh6avQdIOJ6NO20-Z3h8iRUEFM8VtVEzxSPhjcOqrk,2269
8
- sqlcompose-0.0.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
9
- sqlcompose-0.0.0.dist-info/entry_points.txt,sha256=N3eGXJzvw5yx5WgFFmX0L6Nw0-ojm90OfhtlC-C6t_4,51
10
- sqlcompose-0.0.0.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- sqlcompose=sqlcompose.main:main
3
-