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