sqlcompose 0.0.2__py3-none-any.whl → 0.0.4__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 +3 -3
- sqlcompose/__main__.py +4 -2
- sqlcompose/core/app.py +16 -8
- sqlcompose/core/compat.py +45 -2
- sqlcompose/core/file_not_found_err.py +11 -0
- sqlcompose/core/functions.py +8 -6
- sqlcompose-0.0.4.dist-info/METADATA +268 -0
- sqlcompose-0.0.4.dist-info/RECORD +15 -0
- {sqlcompose-0.0.2.dist-info → sqlcompose-0.0.4.dist-info}/WHEEL +1 -1
- sqlcompose-0.0.4.dist-info/entry_points.txt +2 -0
- {sqlcompose-0.0.2.dist-info → sqlcompose-0.0.4.dist-info}/licenses/LICENSE +1 -1
- sqlcompose-0.0.2.dist-info/METADATA +0 -111
- sqlcompose-0.0.2.dist-info/RECORD +0 -14
- sqlcompose-0.0.2.dist-info/entry_points.txt +0 -2
- {sqlcompose-0.0.2.dist-info → sqlcompose-0.0.4.dist-info}/top_level.txt +0 -0
sqlcompose/__init__.py
CHANGED
|
@@ -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
|
-
|
|
10
|
-
__version__ = "0.0.2"
|
|
9
|
+
'FileNotFoundErr',
|
|
10
|
+
]
|
sqlcompose/__main__.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from sys import argv, exit, stderr, stdout
|
|
2
2
|
from sqlcompose.core.app import app
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
if __name__ == "__main__":
|
|
4
|
+
def main(): # pragma: no cover
|
|
6
5
|
result, code = app(argv[1:])
|
|
7
6
|
|
|
8
7
|
if code == 0:
|
|
@@ -11,3 +10,6 @@ if __name__ == "__main__":
|
|
|
11
10
|
stderr.write(result)
|
|
12
11
|
|
|
13
12
|
exit(code)
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
main()
|
sqlcompose/core/app.py
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
from os import path
|
|
2
1
|
from argparse import ArgumentParser
|
|
3
2
|
|
|
4
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
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
def app(args: list[str]) -> tuple[str, int
|
|
8
|
+
def app(args: list[str]) -> tuple[str, int]:
|
|
8
9
|
try:
|
|
9
|
-
|
|
10
|
-
parser.add_argument("input", type=str, help = "SQL expression or location of an SQL file")
|
|
11
|
-
pargs = parser.parse_args(args)
|
|
10
|
+
from sys import stdin # iport must be done here to allow for test patching
|
|
12
11
|
|
|
13
|
-
if
|
|
14
|
-
sql =
|
|
12
|
+
if not stdin.isatty() and ( piped := get_piped_input(stdin) ):
|
|
13
|
+
sql = loads(piped)
|
|
15
14
|
else:
|
|
16
|
-
|
|
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)
|
|
17
23
|
|
|
18
24
|
return sql, 0
|
|
19
25
|
except SystemExit as ex:
|
|
20
26
|
return str(ex), 2
|
|
27
|
+
except (FileNotFoundErr, FileNotFoundError) as ex:
|
|
28
|
+
return str(ex), 3
|
|
21
29
|
except Exception as ex: # pragma: no cover
|
|
22
30
|
return f"Unexpected error: {ex}", 1
|
sqlcompose/core/compat.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
from os import path
|
|
1
|
+
from os import path, sep
|
|
2
|
+
from typing import TextIO
|
|
3
|
+
from re import compile
|
|
4
|
+
from threading import Thread, Event
|
|
2
5
|
|
|
6
|
+
RX_FILE = compile(r"^[\w,\s-]+\.[A-Za-z]{3}$")
|
|
3
7
|
WINDOWS_PATH_SEP = "\\"
|
|
4
8
|
UNIX_PATH_SEP = "/"
|
|
5
9
|
|
|
@@ -29,4 +33,43 @@ def get_relative_path(file_path: str, root: str) -> str:
|
|
|
29
33
|
if root == file_path:
|
|
30
34
|
return file_path
|
|
31
35
|
else:
|
|
32
|
-
return path.relpath(file_path, path.commonprefix([root, file_path]))
|
|
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
|
sqlcompose/core/functions.py
CHANGED
|
@@ -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
|
|
40
|
+
raise FileNotFoundErr(filename)
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
with open(filename, "r", encoding="utf-8") as file:
|
|
@@ -77,7 +77,7 @@ def compose(
|
|
|
77
77
|
file_path_inner,
|
|
78
78
|
root,
|
|
79
79
|
level + 1,
|
|
80
|
-
stack
|
|
80
|
+
stack.copy()
|
|
81
81
|
)
|
|
82
82
|
includes.append(
|
|
83
83
|
Include(
|
|
@@ -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
|
|
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
|
|
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)
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlcompose
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Composition of SQL files
|
|
5
|
+
Author-email: Anders Madsen <anders.madsen@alphavue.com>
|
|
6
|
+
License-Expression: 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 :: 3.14
|
|
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.15,>=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
|
+
Requires-Dist: pytest-mock>=3.15; extra == "test"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
|
|
36
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
|
|
37
|
+
[](https://github.com/apmadsen/sqlcompose/releases)
|
|
38
|
+

|
|
39
|
+

|
|
40
|
+
[](https://pepy.tech/projects/sqlcompose)
|
|
41
|
+
|
|
42
|
+
# SQLCompose
|
|
43
|
+
|
|
44
|
+
Composable SQL for Python — designed to make large queries easier to structure, reuse, and maintain.
|
|
45
|
+
|
|
46
|
+
## 🚀 Why this exists
|
|
47
|
+
|
|
48
|
+
As SQL queries grow, they tend to become difficult to manage:
|
|
49
|
+
|
|
50
|
+
- Large queries turn into monolithic files
|
|
51
|
+
- Logic gets duplicated across transformations
|
|
52
|
+
- Small changes become risky and time-consuming
|
|
53
|
+
- Reusability is limited
|
|
54
|
+
|
|
55
|
+
👉 Over time, SQL becomes harder to understand, evolve, and maintain.
|
|
56
|
+
|
|
57
|
+
This project introduces a simple idea:
|
|
58
|
+
|
|
59
|
+
**Treat SQL as composable building blocks instead of monolithic scripts.**
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## ✨ Features
|
|
63
|
+
|
|
64
|
+
- 🧩 Split SQL into reusable components
|
|
65
|
+
- 🔗 Compose queries from smaller building blocks
|
|
66
|
+
- ♻️ Reduce duplication across pipelines
|
|
67
|
+
- 🧠 Improve readability and structure
|
|
68
|
+
- ⚡ Lightweight and framework-agnostic
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## 📦 Use cases
|
|
72
|
+
|
|
73
|
+
This library is especially useful when working with:
|
|
74
|
+
|
|
75
|
+
- Data pipelines
|
|
76
|
+
- ETL/ELT workflows
|
|
77
|
+
- Analytics transformations
|
|
78
|
+
- Data warehouse queries
|
|
79
|
+
- Systems with repeated SQL logic
|
|
80
|
+
|
|
81
|
+
👉 Particularly valuable in environments where SQL is a core part of the architecture.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## 🏗 Core idea
|
|
85
|
+
|
|
86
|
+
Traditional SQL workflows rely on large, self-contained query files.
|
|
87
|
+
|
|
88
|
+
SQLCompose takes a different approach:
|
|
89
|
+
|
|
90
|
+
- Break queries into smaller, focused pieces
|
|
91
|
+
- Reuse common logic across multiple queries
|
|
92
|
+
- Assemble final queries through composition
|
|
93
|
+
|
|
94
|
+
👉 This enables a more modular and maintainable way of working with SQL.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
## 🔄 Reusability & maintainability
|
|
98
|
+
|
|
99
|
+
By introducing composition:
|
|
100
|
+
|
|
101
|
+
- Shared logic can be defined once and reused
|
|
102
|
+
- Changes can be made in one place instead of many
|
|
103
|
+
- Query structure becomes easier to reason about
|
|
104
|
+
|
|
105
|
+
👉 This reduces both duplication and long-term maintenance cost.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## 🧠 Design philosophy
|
|
109
|
+
|
|
110
|
+
This project is built around a few key principles:
|
|
111
|
+
|
|
112
|
+
### 1. Modularity over monoliths
|
|
113
|
+
Large SQL files should be broken into smaller, understandable units.
|
|
114
|
+
|
|
115
|
+
### 2. Reusability by design
|
|
116
|
+
Common logic should be shareable across queries and pipelines.
|
|
117
|
+
|
|
118
|
+
### 3. Simplicity over abstraction
|
|
119
|
+
The goal is not to hide SQL, but to organize it better.
|
|
120
|
+
|
|
121
|
+
### 4. Fit into existing workflows
|
|
122
|
+
The library works alongside existing tools and data platforms.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## 🔗 What this enables
|
|
126
|
+
|
|
127
|
+
With composable SQL, you can:
|
|
128
|
+
|
|
129
|
+
- Build more maintainable data pipelines
|
|
130
|
+
- Reduce duplication across transformations
|
|
131
|
+
- Standardize common query patterns
|
|
132
|
+
- Improve collaboration across teams
|
|
133
|
+
|
|
134
|
+
👉 Particularly useful in growing data platforms.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
## ⚖️ Trade-offs
|
|
138
|
+
|
|
139
|
+
| Focus ✅ | Not a goal ❌ |
|
|
140
|
+
|---------|--------------|
|
|
141
|
+
| SQL composability | Full query engine abstraction |
|
|
142
|
+
| Maintainability | Replacing SQL with another DSL |
|
|
143
|
+
| Simplicity | Complex orchestration frameworks |
|
|
144
|
+
|
|
145
|
+
👉 The goal is not to replace SQL —
|
|
146
|
+
but to make it **easier to structure at scale**.
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
## 🧠 Context
|
|
150
|
+
|
|
151
|
+
This library fits into a broader focus on:
|
|
152
|
+
|
|
153
|
+
- Data engineering workflows
|
|
154
|
+
- Clean architecture principles
|
|
155
|
+
- Composable systems
|
|
156
|
+
- Reducing duplication in large codebases
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## 🎯 When to use
|
|
160
|
+
|
|
161
|
+
Use this library if you:
|
|
162
|
+
|
|
163
|
+
- Work with large or growing SQL codebases
|
|
164
|
+
- Reuse logic across multiple queries
|
|
165
|
+
- Maintain data pipelines or transformations
|
|
166
|
+
- Want cleaner, more modular SQL
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
## 🚫 When not to use
|
|
170
|
+
|
|
171
|
+
This library may not be necessary if:
|
|
172
|
+
|
|
173
|
+
- Your SQL queries are small and simple
|
|
174
|
+
- Reusability is not a concern
|
|
175
|
+
- Query complexity is low
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
## 🔗 Related projects
|
|
179
|
+
|
|
180
|
+
Part of a broader focus on:
|
|
181
|
+
|
|
182
|
+
- Runtime abstractions
|
|
183
|
+
- Developer tooling
|
|
184
|
+
- Structured Python systems
|
|
185
|
+
|
|
186
|
+
👉 https://github.com/apmadsen
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
## 🤝 Contributing
|
|
190
|
+
|
|
191
|
+
Feedback, ideas, and contributions are welcome!
|
|
192
|
+
|
|
193
|
+
## ⚙️ Examples
|
|
194
|
+
|
|
195
|
+
### 1. Execute the script with the filename as an argument and output to the console:
|
|
196
|
+
```bash
|
|
197
|
+
sqlcompose query.sql
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 2. Pipe data into application and output to a file
|
|
201
|
+
```bash
|
|
202
|
+
cat query.sql | sqlcompose > output.sql
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3. Execute the script with SQL string as argument
|
|
206
|
+
```bash
|
|
207
|
+
sqlcompose 'select * from $INCLUDE(included-query1.sql)'
|
|
208
|
+
```
|
|
209
|
+
> NOTE: Different consoles have different limitations, so you may have to switch from single to double quotes to allow for using the dollar sign.
|
|
210
|
+
|
|
211
|
+
### 4. Import it in another python application or package
|
|
212
|
+
```python
|
|
213
|
+
from sqlcompose import load, loads
|
|
214
|
+
# method 1 : loading from a file
|
|
215
|
+
sql1 = load("query.sql")
|
|
216
|
+
|
|
217
|
+
# method 2 : loading from an SQL string
|
|
218
|
+
sql2 = loads("""
|
|
219
|
+
select *
|
|
220
|
+
from dataset.table main
|
|
221
|
+
inner join $INCLUDE(other.sql) other
|
|
222
|
+
on other.field = main.field
|
|
223
|
+
""")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Preparing SQL scripts
|
|
227
|
+
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.
|
|
228
|
+
|
|
229
|
+
```sql
|
|
230
|
+
--main-query.sql
|
|
231
|
+
select * from $INCLUDE(includes\included-query2.sql)
|
|
232
|
+
```
|
|
233
|
+
```sql
|
|
234
|
+
--included-query1.sql
|
|
235
|
+
select 1 as test
|
|
236
|
+
```
|
|
237
|
+
```sql
|
|
238
|
+
--included-query2.sql
|
|
239
|
+
select * from $INCLUDE(included-query1.sql)
|
|
240
|
+
union all
|
|
241
|
+
select * from $INCLUDE(nested\included-query3.sql)
|
|
242
|
+
```
|
|
243
|
+
```sql
|
|
244
|
+
--nested\included-query3.sql
|
|
245
|
+
select 1 as test
|
|
246
|
+
```
|
|
247
|
+
Which outputs:
|
|
248
|
+
```sql
|
|
249
|
+
WITH Q_1_1 AS (
|
|
250
|
+
WITH Q_2_1 AS (
|
|
251
|
+
--includes\included-query1.sql
|
|
252
|
+
select 1 as test
|
|
253
|
+
), Q_2_2 AS (
|
|
254
|
+
--includes\nested\included-query3.sql
|
|
255
|
+
select 1 as test
|
|
256
|
+
), Q_2 AS (
|
|
257
|
+
--includes\included-query2.sql
|
|
258
|
+
select * from Q_2_1
|
|
259
|
+
union all
|
|
260
|
+
select * from Q_2_2
|
|
261
|
+
)
|
|
262
|
+
SELECT * FROM Q_2
|
|
263
|
+
), Q_1 AS (
|
|
264
|
+
--test\main-query.sql
|
|
265
|
+
select * from Q_1_1
|
|
266
|
+
)
|
|
267
|
+
SELECT * FROM Q_1
|
|
268
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
sqlcompose/__init__.py,sha256=Mzbbnsg3PdghoXky2pcA3huMMviKDU5JW2byxsOMzh4,284
|
|
2
|
+
sqlcompose/__main__.py,sha256=wqRxieqGQJxbNIrFiN5YcbbvFaQ7SkBUPL7-GaLXZCI,285
|
|
3
|
+
sqlcompose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
sqlcompose/core/app.py,sha256=olAB98MrHYjeHoeK51WaOYxeDxavzweizK12Q9YZwdU,1057
|
|
5
|
+
sqlcompose/core/circular_dependency_error.py,sha256=3JqXYqX4Qg5QC4te2IuiqU2snC2oU--g9m1qFah4A38,49
|
|
6
|
+
sqlcompose/core/compat.py,sha256=O_cGHvvv7fT7b5r9vOM469663ByBy3w5IKI6od87x_4,1952
|
|
7
|
+
sqlcompose/core/file_not_found_err.py,sha256=NjrHMEbYEoRSBNz-nrU2p-tHZMs_9SOiAHgElGKBBtw,328
|
|
8
|
+
sqlcompose/core/functions.py,sha256=JfT9Ob0B11YoQm4Yf0mPMwt3Uq1tPkAxDS-8npOtiLo,3796
|
|
9
|
+
sqlcompose/core/include.py,sha256=0k5hyO0U_cYbj8iByIQoOF_YQb3kdRgegZVsYiMU2Vg,560
|
|
10
|
+
sqlcompose-0.0.4.dist-info/licenses/LICENSE,sha256=3BXKzoVEhUaMXZdh_jFn3cVMWD3q1dk44kE8Cn0XUas,1069
|
|
11
|
+
sqlcompose-0.0.4.dist-info/METADATA,sha256=5Ab_VMNjkavEVYzZLHWZzwEU2r0HA1R06WWlrxhcqn8,7648
|
|
12
|
+
sqlcompose-0.0.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
sqlcompose-0.0.4.dist-info/entry_points.txt,sha256=YrvDb4_5rGtCoq3LfASDCJq3U4nh1ciUWihDW4yJr3c,56
|
|
14
|
+
sqlcompose-0.0.4.dist-info/top_level.txt,sha256=-8g6qpTAzLcUSsbk3v6mgNbY34pJ2LIplugBVYB5Jq0,11
|
|
15
|
+
sqlcompose-0.0.4.dist-info/RECORD,,
|
|
@@ -1,111 +0,0 @@
|
|
|
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
|
-
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
|
|
35
|
-
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
|
|
36
|
-

|
|
37
|
-

|
|
38
|
-

|
|
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,14 +0,0 @@
|
|
|
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,,
|
|
File without changes
|