sqlcompose 0.0.3__tar.gz → 0.0.4a2__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 (28) hide show
  1. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/LICENSE +1 -1
  2. {sqlcompose-0.0.3/src/sqlcompose.egg-info → sqlcompose-0.0.4a2}/PKG-INFO +27 -12
  3. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/README.md +21 -8
  4. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/pyproject.toml +13 -8
  5. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose/__init__.py +3 -3
  6. sqlcompose-0.0.4a2/src/sqlcompose/core/app.py +30 -0
  7. sqlcompose-0.0.4a2/src/sqlcompose/core/compat.py +75 -0
  8. sqlcompose-0.0.4a2/src/sqlcompose/core/file_not_found_err.py +11 -0
  9. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose/core/functions.py +7 -5
  10. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2/src/sqlcompose.egg-info}/PKG-INFO +27 -12
  11. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose.egg-info/SOURCES.txt +1 -0
  12. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose.egg-info/requires.txt +1 -0
  13. sqlcompose-0.0.4a2/tests/test_app.py +48 -0
  14. sqlcompose-0.0.4a2/tests/test_compat.py +48 -0
  15. sqlcompose-0.0.4a2/tests/test_functions.py +42 -0
  16. sqlcompose-0.0.3/src/sqlcompose/core/app.py +0 -22
  17. sqlcompose-0.0.3/src/sqlcompose/core/compat.py +0 -32
  18. sqlcompose-0.0.3/tests/test_app.py +0 -29
  19. sqlcompose-0.0.3/tests/test_compat.py +0 -52
  20. sqlcompose-0.0.3/tests/test_functions.py +0 -40
  21. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/setup.cfg +0 -0
  22. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose/__main__.py +0 -0
  23. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose/core/circular_dependency_error.py +0 -0
  24. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose/core/include.py +0 -0
  25. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose/py.typed +0 -0
  26. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose.egg-info/dependency_links.txt +0 -0
  27. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose.egg-info/entry_points.txt +0 -0
  28. {sqlcompose-0.0.3 → sqlcompose-0.0.4a2}/src/sqlcompose.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Dagrofa
3
+ Copyright (c) 2025 Anders Madsen
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlcompose
3
- Version: 0.0.3
4
- Summary: Composition of linked SQL files
3
+ Version: 0.0.4a2
4
+ Summary: Composition of SQL files
5
5
  Author-email: Anders Madsen <anders.madsen@alphavue.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: repository, https://github.com/apmadsen/sqlcompose
8
8
  Keywords: sql,composition,windows,linux
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -18,39 +18,54 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Programming Language :: Python
22
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
24
  Classifier: Topic :: Software Development :: Libraries
24
25
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
26
+ Requires-Python: <3.15,>=3.10
26
27
  Description-Content-Type: text/markdown
27
28
  License-File: LICENSE
28
29
  Provides-Extra: test
29
30
  Requires-Dist: pytest>=8.3.0; extra == "test"
30
31
  Requires-Dist: pytest-cov>=6.1.0; extra == "test"
32
+ Requires-Dist: pytest-mock>=3.15; extra == "test"
31
33
  Dynamic: license-file
32
34
 
33
35
  [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
34
36
  [![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)
37
+ [![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)](https://github.com/apmadsen/sqlcompose/releases)
36
38
  ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
37
39
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
40
+ [![PyPI Downloads](https://static.pepy.tech/badge/sqlcompose/week)](https://pepy.tech/projects/sqlcompose)
38
41
 
42
+ # sqlcompose: Composition of SQL files
43
+ sqlcompose lets you to compose sql files from multiple files by introducing `$INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
39
44
 
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.
45
+ Using composition, you reduce both complexity and duplication of code thus adhering to the DRY principle.
46
+
47
+ ### SQL Dialect
48
+ sqlcompose outputs SQL as standard ANSI SQL. Note though, that no validation is done on either the input or the output.
42
49
 
43
50
  ## Examples
44
- __Execute the script directly:__
45
- ```console
51
+
52
+ ### 1. Execute the script with the filename as an argument and output to the console:
53
+ ```bash
46
54
  sqlcompose query.sql
47
55
  ```
56
+
57
+ ### 2. Pipe data into application and output to a file
58
+ ```bash
59
+ cat query.sql | sqlcompose > output.sql
60
+ ```
61
+
62
+ ### 3. Execute the script with SQL string as argument
48
63
  ```bash
49
- sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
50
- sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
64
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)'
51
65
  ```
66
+ > NOTE: Different consoles have different limitations, so you may have to switch from single to double quotes to allow for using the dollar sign.
52
67
 
53
- __Import it in another script:__
68
+ ### 4. Import it in another python application or package
54
69
  ```python
55
70
  from sqlcompose import load, loads
56
71
  # method 1 : loading from a file
@@ -1,24 +1,37 @@
1
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
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)
3
+ [![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)](https://github.com/apmadsen/sqlcompose/releases)
4
4
  ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
5
5
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
6
+ [![PyPI Downloads](https://static.pepy.tech/badge/sqlcompose/week)](https://pepy.tech/projects/sqlcompose)
6
7
 
8
+ # sqlcompose: Composition of SQL files
9
+ sqlcompose lets you to compose sql files from multiple files by introducing `$INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
7
10
 
8
- # sqlcompose: Composition of linked SQL files
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.
11
+ Using composition, you reduce both complexity and duplication of code thus adhering to the DRY principle.
12
+
13
+ ### SQL Dialect
14
+ sqlcompose outputs SQL as standard ANSI SQL. Note though, that no validation is done on either the input or the output.
10
15
 
11
16
  ## Examples
12
- __Execute the script directly:__
13
- ```console
17
+
18
+ ### 1. Execute the script with the filename as an argument and output to the console:
19
+ ```bash
14
20
  sqlcompose query.sql
15
21
  ```
22
+
23
+ ### 2. Pipe data into application and output to a file
24
+ ```bash
25
+ cat query.sql | sqlcompose > output.sql
26
+ ```
27
+
28
+ ### 3. Execute the script with SQL string as argument
16
29
  ```bash
17
- sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
18
- sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
30
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)'
19
31
  ```
32
+ > NOTE: Different consoles have different limitations, so you may have to switch from single to double quotes to allow for using the dollar sign.
20
33
 
21
- __Import it in another script:__
34
+ ### 4. Import it in another python application or package
22
35
  ```python
23
36
  from sqlcompose import load, loads
24
37
  # method 1 : loading from a file
@@ -1,13 +1,14 @@
1
1
  [project]
2
2
  name = "sqlcompose"
3
3
  dynamic = ["version"]
4
- description = "Composition of linked SQL files"
4
+ description = "Composition of SQL files"
5
5
  keywords = ["sql", "composition", "windows", "linux"]
6
6
  readme = "README.md"
7
7
  authors = [
8
8
  { name = "Anders Madsen", email = "anders.madsen@alphavue.com" }
9
9
  ]
10
- license = { text = "MIT" }
10
+ license = "MIT"
11
+ license-files = [ "LICENSE"]
11
12
  classifiers = [
12
13
  "Development Status :: 5 - Production/Stable",
13
14
  "Development Status :: 6 - Mature",
@@ -21,13 +22,16 @@ classifiers = [
21
22
  "Programming Language :: Python :: 3.11",
22
23
  "Programming Language :: Python :: 3.12",
23
24
  "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
24
26
  "Programming Language :: Python",
25
27
  "Topic :: Software Development :: Libraries :: Python Modules",
26
28
  "Topic :: Software Development :: Libraries",
27
29
  "Typing :: Typed"
28
30
  ]
29
- dependencies = []
30
- requires-python = ">=3.10"
31
+ dependencies = [
32
+
33
+ ]
34
+ requires-python = ">= 3.10, < 3.15"
31
35
 
32
36
  [project.urls]
33
37
  repository = "https://github.com/apmadsen/sqlcompose"
@@ -39,11 +43,12 @@ repository = "https://github.com/apmadsen/sqlcompose"
39
43
  test = [
40
44
  "pytest>=8.3.0",
41
45
  "pytest-cov>=6.1.0",
46
+ "pytest-mock>=3.15"
42
47
  ]
43
48
 
44
- [tool.setuptools.dynamic]
45
- version = { attr = 'sqlcompose.__init__.__version__' }
49
+ [tool.setuptools-git-versioning]
50
+ enabled = true
46
51
 
47
52
  [build-system]
48
- requires = ["setuptools >= 77.0.3"]
49
- build-backend = "setuptools.build_meta"
53
+ requires = ["setuptools >= 77.0.3", "setuptools-git-versioning >= 2.1.0"]
54
+ build-backend = "setuptools.build_meta"
@@ -1,10 +1,10 @@
1
1
  from sqlcompose.core.functions import load, loads
2
2
  from sqlcompose.core.circular_dependency_error import CircularDependencyError
3
+ from sqlcompose.core.file_not_found_err import FileNotFoundErr
3
4
 
4
5
  __all__ = [
5
6
  'load',
6
7
  'loads',
7
8
  'CircularDependencyError',
8
- ]
9
- __package_name__ = "sqlcompose"
10
- __version__ = "0.0.3"
9
+ 'FileNotFoundErr',
10
+ ]
@@ -0,0 +1,30 @@
1
+ from argparse import ArgumentParser
2
+
3
+ from sqlcompose.core.functions import load, loads
4
+ from sqlcompose.core.compat import is_file, get_piped_input
5
+ from sqlcompose.core.file_not_found_err import FileNotFoundErr
6
+
7
+
8
+ def app(args: list[str]) -> tuple[str, int]:
9
+ try:
10
+ from sys import stdin # iport must be done here to allow for test patching
11
+
12
+ if not stdin.isatty() and ( piped := get_piped_input(stdin) ):
13
+ sql = loads(piped)
14
+ else:
15
+ parser = ArgumentParser(prog = "sqlcompose")
16
+ parser.add_argument("input", type=str, help = "SQL expression or location of an SQL file")
17
+ pargs = parser.parse_args(args)
18
+
19
+ if is_file(pargs.input):
20
+ sql = load(pargs.input)
21
+ else:
22
+ sql = loads(pargs.input)
23
+
24
+ return sql, 0
25
+ except SystemExit as ex:
26
+ return str(ex), 2
27
+ except (FileNotFoundErr, FileNotFoundError) as ex:
28
+ return str(ex), 3
29
+ except Exception as ex: # pragma: no cover
30
+ return f"Unexpected error: {ex}", 1
@@ -0,0 +1,75 @@
1
+ from os import path, sep
2
+ from typing import TextIO
3
+ from re import compile
4
+ from threading import Thread, Event
5
+
6
+ RX_FILE = compile(r"^[\w,\s-]+\.[A-Za-z]{3}$")
7
+ WINDOWS_PATH_SEP = "\\"
8
+ UNIX_PATH_SEP = "/"
9
+
10
+ def fix_path(file_path: str) -> str:
11
+ """Replaces all path separators, be they Linux or Windows style
12
+ to the standard path separator of the system.
13
+
14
+ Args:
15
+ file_path (str): The file path to fix.
16
+ """
17
+ for str in [ WINDOWS_PATH_SEP, UNIX_PATH_SEP ]:
18
+ if str != path.sep:
19
+ file_path = file_path.replace(str, path.sep)
20
+
21
+ return file_path
22
+
23
+ def get_relative_path(file_path: str, root: str) -> str:
24
+ """Get the path relative to root path.
25
+
26
+ Args:
27
+ file_path (str): The path.
28
+ root (str): The root path.
29
+
30
+ Returns:
31
+ str: The relative path.
32
+ """
33
+ if root == file_path:
34
+ return file_path
35
+ else:
36
+ return path.relpath(file_path, path.commonprefix([root, file_path]))
37
+
38
+ def is_file(text: str) -> bool:
39
+ if path.isfile(text):
40
+ return True
41
+ elif sep in text and is_file(path.basename(text)):
42
+ return True
43
+ elif RX_FILE.match(text):
44
+ return True
45
+
46
+ return False
47
+
48
+ def get_piped_input(pipe: TextIO) -> str:
49
+ """Reads input from pipe (usually sys.stdin) without blocking.
50
+ """
51
+ output: list[str] = []
52
+ ev_started = Event()
53
+ ev_done = Event()
54
+
55
+ def fn(output: list[str]):
56
+ try:
57
+ ev_started.set()
58
+ output.append(pipe.read())
59
+ except OSError:
60
+ pass
61
+ finally:
62
+ ev_done.set()
63
+
64
+ thread = Thread(target = fn, args = (output,))
65
+ thread.start()
66
+
67
+ ev_started.wait()
68
+
69
+ if ev_done.wait(0.1): # if there is in fact anything in the pipe, we expect it to be read within 0.1 second
70
+ thread.join()
71
+ else:
72
+ pass # pragma: no cover
73
+ # the thread will block indefinitely, nothing to do about it
74
+
75
+ return output[0] if any(output) else ""
@@ -0,0 +1,11 @@
1
+
2
+ class FileNotFoundErr(Exception):
3
+ __slots__ = [ "__filename" ]
4
+
5
+ def __init__(self, filename: str, error: str | None = None):
6
+ super().__init__(error or f"File {filename} not found")
7
+ self.__filename = filename
8
+
9
+ @property
10
+ def filename(self) -> str:
11
+ return self.__filename # pragma: no cover
@@ -1,11 +1,11 @@
1
1
  from os import path
2
2
  from typing import Sequence
3
- from os import path
4
3
  from re import compile, sub, escape, IGNORECASE
5
4
  from textwrap import indent
6
5
 
7
6
  from sqlcompose.core.circular_dependency_error import CircularDependencyError
8
7
  from sqlcompose.core.include import Include
8
+ from sqlcompose.core.file_not_found_err import FileNotFoundErr
9
9
  from sqlcompose.core.compat import fix_path, get_relative_path
10
10
 
11
11
  REGEX_INCLUDE = compile(r"\$INCLUDE\(([^\)]+)\)", IGNORECASE)
@@ -37,7 +37,7 @@ def load(filename: str) -> str:
37
37
  filename = fix_path(filename)
38
38
 
39
39
  if not path.isfile(filename):
40
- raise FileNotFoundError(filename)
40
+ raise FileNotFoundErr(filename)
41
41
 
42
42
 
43
43
  with open(filename, "r", encoding="utf-8") as file:
@@ -88,11 +88,13 @@ def compose(
88
88
  )
89
89
  )
90
90
  index = index + 1
91
- except FileNotFoundError:
91
+ except (FileNotFoundErr, FileNotFoundError) as ex:
92
+ filename = get_relative_path(file_path_inner, root)
92
93
  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
+ raise FileNotFoundErr(filename, f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" which was referred to in \"{get_relative_path(parent, root)}\", was not found...") from ex
94
95
  else:
95
- raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" was not found...")
96
+ raise FileNotFoundErr(filename, f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" was not found...") from ex
97
+
96
98
 
97
99
  for include in includes:
98
100
  sql = sub(escape(include.match), include.name, sql)
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlcompose
3
- Version: 0.0.3
4
- Summary: Composition of linked SQL files
3
+ Version: 0.0.4a2
4
+ Summary: Composition of SQL files
5
5
  Author-email: Anders Madsen <anders.madsen@alphavue.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: repository, https://github.com/apmadsen/sqlcompose
8
8
  Keywords: sql,composition,windows,linux
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -18,39 +18,54 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Programming Language :: Python
22
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
24
  Classifier: Topic :: Software Development :: Libraries
24
25
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
26
+ Requires-Python: <3.15,>=3.10
26
27
  Description-Content-Type: text/markdown
27
28
  License-File: LICENSE
28
29
  Provides-Extra: test
29
30
  Requires-Dist: pytest>=8.3.0; extra == "test"
30
31
  Requires-Dist: pytest-cov>=6.1.0; extra == "test"
32
+ Requires-Dist: pytest-mock>=3.15; extra == "test"
31
33
  Dynamic: license-file
32
34
 
33
35
  [![Test](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
34
36
  [![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)
37
+ [![Stable Version](https://img.shields.io/pypi/v/sqlcompose?label=stable&sort=semver&color=blue)](https://github.com/apmadsen/sqlcompose/releases)
36
38
  ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/sqlcompose?label=pre-release&include_prereleases&sort=semver&color=blue)
37
39
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sqlcompose)
40
+ [![PyPI Downloads](https://static.pepy.tech/badge/sqlcompose/week)](https://pepy.tech/projects/sqlcompose)
38
41
 
42
+ # sqlcompose: Composition of SQL files
43
+ sqlcompose lets you to compose sql files from multiple files by introducing `$INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
39
44
 
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.
45
+ Using composition, you reduce both complexity and duplication of code thus adhering to the DRY principle.
46
+
47
+ ### SQL Dialect
48
+ sqlcompose outputs SQL as standard ANSI SQL. Note though, that no validation is done on either the input or the output.
42
49
 
43
50
  ## Examples
44
- __Execute the script directly:__
45
- ```console
51
+
52
+ ### 1. Execute the script with the filename as an argument and output to the console:
53
+ ```bash
46
54
  sqlcompose query.sql
47
55
  ```
56
+
57
+ ### 2. Pipe data into application and output to a file
58
+ ```bash
59
+ cat query.sql | sqlcompose > output.sql
60
+ ```
61
+
62
+ ### 3. Execute the script with SQL string as argument
48
63
  ```bash
49
- sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
50
- sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
64
+ sqlcompose 'select * from $INCLUDE(included-query1.sql)'
51
65
  ```
66
+ > NOTE: Different consoles have different limitations, so you may have to switch from single to double quotes to allow for using the dollar sign.
52
67
 
53
- __Import it in another script:__
68
+ ### 4. Import it in another python application or package
54
69
  ```python
55
70
  from sqlcompose import load, loads
56
71
  # method 1 : loading from a file
@@ -13,6 +13,7 @@ src/sqlcompose.egg-info/top_level.txt
13
13
  src/sqlcompose/core/app.py
14
14
  src/sqlcompose/core/circular_dependency_error.py
15
15
  src/sqlcompose/core/compat.py
16
+ src/sqlcompose/core/file_not_found_err.py
16
17
  src/sqlcompose/core/functions.py
17
18
  src/sqlcompose/core/include.py
18
19
  tests/test_app.py
@@ -2,3 +2,4 @@
2
2
  [test]
3
3
  pytest>=8.3.0
4
4
  pytest-cov>=6.1.0
5
+ pytest-mock>=3.15
@@ -0,0 +1,48 @@
1
+ # ruff: noqa
2
+ # pyright: basic
3
+ from os import path, chdir, curdir
4
+ from sys import stdin
5
+ from io import StringIO
6
+ from pytest import fixture, raises as assert_raises
7
+ from pytest_mock import MockerFixture
8
+
9
+ from sqlcompose.core.app import app
10
+
11
+
12
+ def test_missing_args():
13
+ _result, code = app([])
14
+ assert code == 2
15
+
16
+ def test_existing_file_by_path():
17
+ result, code = app([path.join("tests", "main-query.sql")])
18
+ assert len(result) > 0
19
+ assert code == 0
20
+
21
+ def test_missing_file_by_path():
22
+ result, code = app([path.join("tests", "nonexisting.sql")])
23
+ assert len(result) > 0
24
+ assert code == 3
25
+
26
+ result, code = app([f"SELECT * FROM $INCLUDE({path.join('tests', 'nonexisting.sql')})"])
27
+ assert len(result) > 0
28
+ assert code == 3
29
+
30
+
31
+ def test_sql():
32
+ result, code = app([f"SELECT * FROM $INCLUDE({path.join('tests', 'main-query.sql')})"])
33
+ assert len(result) > 0
34
+ assert code == 0
35
+
36
+ def test_pipe(mocker: MockerFixture):
37
+ chdir("tests")
38
+ try:
39
+ with open("main-query.sql", "rt", encoding="utf8") as input:
40
+ mocker.patch(f"sys.stdin", input)
41
+ try:
42
+ result, code = app([])
43
+ assert len(result) > 0
44
+ assert code == 0
45
+ finally:
46
+ mocker.resetall()
47
+ finally:
48
+ chdir("..")
@@ -0,0 +1,48 @@
1
+ # ruff: noqa
2
+ # pyright: basic
3
+ from pytest import fixture, raises as assert_raises
4
+ from sys import platform
5
+
6
+ from sqlcompose.core.compat import fix_path, get_relative_path
7
+
8
+
9
+ def test_fix_path():
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
+ assert result == expected_result
24
+
25
+
26
+ def test_get_relative_path():
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
+ assert 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
+ assert 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
+ assert result == expected_result
48
+
@@ -0,0 +1,42 @@
1
+ # ruff: noqa
2
+ # pyright: basic
3
+ from pytest import fixture, raises as assert_raises
4
+
5
+ from sqlcompose import loads, load, CircularDependencyError, FileNotFoundErr
6
+ from sqlcompose.core.functions import compose
7
+
8
+
9
+ def test_nonexisting():
10
+ with assert_raises(FileNotFoundErr):
11
+ load("nonexisting.sql")
12
+ with assert_raises(FileNotFoundErr):
13
+ load("tests/non_existing_include.sql")
14
+ with assert_raises(FileNotFoundErr):
15
+ compose("select * from $INCLUDE(some_file_that_does_not_exist.sql)", "SQL", ".", ".")
16
+
17
+
18
+ filename = "tests/existing_include.sql"
19
+ with open(filename, "r", encoding="utf-8") as file:
20
+ with assert_raises(FileNotFoundErr):
21
+ compose(file.read(), filename, filename, ".")
22
+
23
+ def test_reuse_composition():
24
+ result = compose("select * from $INCLUDE(tests/includes/included-query3.sql)", "SQL", ".", ".")
25
+ # print(result)
26
+ assert len(result) > 0
27
+
28
+
29
+ def test_existing_file_by_path():
30
+ result = load("tests/main-query.sql")
31
+ # print(result)
32
+ assert len(result) > 0
33
+
34
+ def test_sql():
35
+ result = loads("SELECT * FROM $INCLUDE(tests/main-query.sql)")
36
+ # print(result)
37
+ assert len(result) > 0
38
+
39
+ def test_circular_dependency():
40
+ with assert_raises(CircularDependencyError):
41
+ load("tests/circular_left.sql")
42
+
@@ -1,22 +0,0 @@
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
@@ -1,32 +0,0 @@
1
- from os import path
2
-
3
- WINDOWS_PATH_SEP = "\\"
4
- UNIX_PATH_SEP = "/"
5
-
6
- def fix_path(file_path: str) -> str:
7
- """Replaces all path separators, be they Linux or Windows style
8
- to the standard path separator of the system.
9
-
10
- Args:
11
- file_path (str): The file path to fix.
12
- """
13
- for str in [ WINDOWS_PATH_SEP, UNIX_PATH_SEP ]:
14
- if str != path.sep:
15
- file_path = file_path.replace(str, path.sep)
16
-
17
- return file_path
18
-
19
- def get_relative_path(file_path: str, root: str) -> str:
20
- """Get the path relative to root path.
21
-
22
- Args:
23
- file_path (str): The path.
24
- root (str): The root path.
25
-
26
- Returns:
27
- str: The relative path.
28
- """
29
- if root == file_path:
30
- return file_path
31
- else:
32
- return path.relpath(file_path, path.commonprefix([root, file_path]))
@@ -1,29 +0,0 @@
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
-
@@ -1,52 +0,0 @@
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
-
@@ -1,40 +0,0 @@
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
-
File without changes