pysqlscribe 0.0.1__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.
- pysqlscribe-0.0.1/.gitignore +13 -0
- pysqlscribe-0.0.1/LICENSE +20 -0
- pysqlscribe-0.0.1/PKG-INFO +192 -0
- pysqlscribe-0.0.1/README.md +182 -0
- pysqlscribe-0.0.1/pyproject.toml +34 -0
- pysqlscribe-0.0.1/pysqlscribe/__init__.py +0 -0
- pysqlscribe-0.0.1/pysqlscribe/query.py +249 -0
- pysqlscribe-0.0.1/pysqlscribe/regex_patterns.py +5 -0
- pysqlscribe-0.0.1/pysqlscribe/schema.py +60 -0
- pysqlscribe-0.0.1/pysqlscribe/table.py +83 -0
- pysqlscribe-0.0.1/uv.lock +277 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Max Pumperla
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pysqlscribe
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A simple Python Library for building relational database queries using objects
|
|
5
|
+
Project-URL: Source code, https://github.com/danielenricocahall/pysqlscribe
|
|
6
|
+
Author-email: Daniel Cahall <danielenricocahall@gmail.com>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Overview
|
|
12
|
+
[](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11-blue)
|
|
13
|
+
|
|
14
|
+
This is `pysqlscribe`, the Python library intended to make building SQL queries in your code a bit easier!
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Motivation
|
|
18
|
+
Other query building libraries, such as [pypika](https://github.com/kayak/pypika) are fantastic but not actively maintained. Some ORM libraries such as `sqlalchemy` offer similar (and awesome) capabilities using the core API, but if you're not already using the library in your application, it's a bit of a large dependency to introduce for the purposes of query building.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# API
|
|
22
|
+
|
|
23
|
+
`pysqlscribe` currently offers several APIs for building queries.
|
|
24
|
+
|
|
25
|
+
## Query
|
|
26
|
+
|
|
27
|
+
A `Query` object can be constructed using the `QueryRegistry`'s `get_builder` if you supply a valid dialect (e.g; "mysql", "postgres", "oracle). For example, "mysql" would be:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from pysqlscribe.query import QueryRegistry
|
|
31
|
+
|
|
32
|
+
query_builder = QueryRegistry.get_builder("mysql")
|
|
33
|
+
query = query_builder.select("test_field", "another_test_field").from_("test_table").build()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Alternatively, you can create the corresponding `Query` class associated with the dialect directly:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pysqlscribe.query import MySQLQuery
|
|
40
|
+
|
|
41
|
+
query_builder = MySQLQuery()
|
|
42
|
+
query = query_builder.select("test_field", "another_test_field").from_("test_table").build()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Furthermore, if there are any dialects that we currently don't support, you can create your own by subclassing `Query` and registering it with the `QueryRegistry`:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from pysqlscribe.query import QueryRegistry, Query
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@QueryRegistry.register("custom")
|
|
53
|
+
class CustomQuery(Query):
|
|
54
|
+
...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Table
|
|
58
|
+
An alternative method for building queries is through the `Table` object:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pysqlscribe.table import MySQLTable
|
|
62
|
+
|
|
63
|
+
table = MySQLTable("test_table", "test_field", "another_test_field")
|
|
64
|
+
query = table.select("test_field").build()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A schema for the table can also be provided as a keyword argument, after the columns/fields:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from pysqlscribe.table import MySQLTable
|
|
71
|
+
|
|
72
|
+
table = MySQLTable("test_table", "test_field", "another_test_field", schema="test_schema")
|
|
73
|
+
query = table.select("test_field").build()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Additionally, in the event an invalid field is provided in the `select` call, we will raise an exception:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from pysqlscribe.table import MySQLTable
|
|
80
|
+
|
|
81
|
+
table = MySQLTable("test_table", "test_field", "another_test_field")
|
|
82
|
+
table.select("some_nonexistent_field") # will raise InvalidFieldsException
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`Table` also offers a `create` method in the event you've added a new dialect which doesn't have an associated `Table` implementation, or if you need to change it for different environments (e.g; `sqlite` for local development, `mysql`/`postgres`/`oracle`/etc. for deployment):
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from pysqlscribe.table import Table
|
|
89
|
+
|
|
90
|
+
new_dialect_table_class = Table.create(
|
|
91
|
+
"new-dialect") # assuming you've registered "new-dialect" with the `QueryRegistry`
|
|
92
|
+
table = new_dialect_table_class("test_table", "test_field", "another_test_field")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
You can overwrite the original fields supplied to a `Table` as well, which will delete the old attributes and set new ones:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from pysqlscribe.table import MySQLTable
|
|
99
|
+
|
|
100
|
+
table = MySQLTable("test_table", "test_field", "another_test_field")
|
|
101
|
+
table.test_field # valid
|
|
102
|
+
table.fields = ['new_test_field']
|
|
103
|
+
table.select("new_test_field")
|
|
104
|
+
table.new_test_field # now valid - but `table.test_field` is not anymore
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
## Schema
|
|
111
|
+
For associating multiple `Table`s with a single schema, you can use the `Schema`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from pysqlscribe.schema import Schema
|
|
115
|
+
|
|
116
|
+
schema = Schema("test_schema", tables=["test_table", "another_test_table"], dialect="postgres")
|
|
117
|
+
schema.tables # a list of two `Table` objects
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This is functionally equivalent to:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from pysqlscribe.table import PostgresTable
|
|
124
|
+
|
|
125
|
+
table = PostgresTable("test_table", schema="test_schema")
|
|
126
|
+
another_table = PostgresTable("another_test_table", schema="test_schema")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Instead of supplying a `dialect` directly to `Schema`, you can also set the environment variable `PYQUERY_BUILDER_DIALECT`:
|
|
130
|
+
|
|
131
|
+
```shell
|
|
132
|
+
export PYQUERY_BUILDER_DIALECT = 'postgres'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from pysqlscribe.schema import Schema
|
|
137
|
+
|
|
138
|
+
schema = Schema("test_schema", tables=["test_table", "another_test_table"])
|
|
139
|
+
schema.tables # a list of two `PostgresTable` objects
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Alternatively, if you already have existing `Table` objects you want to associate with the schema, you can supply them directly (in this case, `dialect` is not needed):
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from pysqlscribe.schema import Schema
|
|
146
|
+
from pysqlscribe.table import PostgresTable
|
|
147
|
+
|
|
148
|
+
table = PostgresTable("test_table")
|
|
149
|
+
another_table = PostgresTable("another_test_table")
|
|
150
|
+
schema = Schema("test_schema", [table, another_table])
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
`Schema` also has each table set as an attribute, so in the example above, you can do the following:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
schema.test_table # will return the supplied table object with the name `"test_table"`
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Contributions
|
|
162
|
+
|
|
163
|
+
Contributions are always welcome, as this is a new and active project.
|
|
164
|
+
|
|
165
|
+
## Local Environment Setup
|
|
166
|
+
This project currently uses `uv` for convenience, although we currently only have dev dependencies. To create your environment:
|
|
167
|
+
```shell
|
|
168
|
+
uv sync
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Unit Testing
|
|
172
|
+
`pytest` is used for all unit testing. To run the unit tests locally (assuming a local environment is set up):
|
|
173
|
+
```shell
|
|
174
|
+
uv run pytest
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Unit tests are executed as part of CI, and the behavior should be consistent with what is observed in local development.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Supported Dialects
|
|
181
|
+
This is anticipated to grow, also there are certainly operations that are missing within dialects.
|
|
182
|
+
- [X] `MySQL`
|
|
183
|
+
- [X] `Oracle`
|
|
184
|
+
- [X] `Postgres`
|
|
185
|
+
- [X] `Sqlite`
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# TODO
|
|
189
|
+
- [ ] Support `JOIN`s
|
|
190
|
+
- [ ] Add more dialects
|
|
191
|
+
- [ ] Support `OFFSET` for Oracle and SQLServer
|
|
192
|
+
- [ ] Add a better abstraction around fields such that we can build expressions from comparisons, etc. as strings are limiting
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
[](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11-blue)
|
|
3
|
+
|
|
4
|
+
This is `pysqlscribe`, the Python library intended to make building SQL queries in your code a bit easier!
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Motivation
|
|
8
|
+
Other query building libraries, such as [pypika](https://github.com/kayak/pypika) are fantastic but not actively maintained. Some ORM libraries such as `sqlalchemy` offer similar (and awesome) capabilities using the core API, but if you're not already using the library in your application, it's a bit of a large dependency to introduce for the purposes of query building.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# API
|
|
12
|
+
|
|
13
|
+
`pysqlscribe` currently offers several APIs for building queries.
|
|
14
|
+
|
|
15
|
+
## Query
|
|
16
|
+
|
|
17
|
+
A `Query` object can be constructed using the `QueryRegistry`'s `get_builder` if you supply a valid dialect (e.g; "mysql", "postgres", "oracle). For example, "mysql" would be:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from pysqlscribe.query import QueryRegistry
|
|
21
|
+
|
|
22
|
+
query_builder = QueryRegistry.get_builder("mysql")
|
|
23
|
+
query = query_builder.select("test_field", "another_test_field").from_("test_table").build()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Alternatively, you can create the corresponding `Query` class associated with the dialect directly:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from pysqlscribe.query import MySQLQuery
|
|
30
|
+
|
|
31
|
+
query_builder = MySQLQuery()
|
|
32
|
+
query = query_builder.select("test_field", "another_test_field").from_("test_table").build()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Furthermore, if there are any dialects that we currently don't support, you can create your own by subclassing `Query` and registering it with the `QueryRegistry`:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pysqlscribe.query import QueryRegistry, Query
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@QueryRegistry.register("custom")
|
|
43
|
+
class CustomQuery(Query):
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Table
|
|
48
|
+
An alternative method for building queries is through the `Table` object:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from pysqlscribe.table import MySQLTable
|
|
52
|
+
|
|
53
|
+
table = MySQLTable("test_table", "test_field", "another_test_field")
|
|
54
|
+
query = table.select("test_field").build()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A schema for the table can also be provided as a keyword argument, after the columns/fields:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from pysqlscribe.table import MySQLTable
|
|
61
|
+
|
|
62
|
+
table = MySQLTable("test_table", "test_field", "another_test_field", schema="test_schema")
|
|
63
|
+
query = table.select("test_field").build()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Additionally, in the event an invalid field is provided in the `select` call, we will raise an exception:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from pysqlscribe.table import MySQLTable
|
|
70
|
+
|
|
71
|
+
table = MySQLTable("test_table", "test_field", "another_test_field")
|
|
72
|
+
table.select("some_nonexistent_field") # will raise InvalidFieldsException
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`Table` also offers a `create` method in the event you've added a new dialect which doesn't have an associated `Table` implementation, or if you need to change it for different environments (e.g; `sqlite` for local development, `mysql`/`postgres`/`oracle`/etc. for deployment):
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pysqlscribe.table import Table
|
|
79
|
+
|
|
80
|
+
new_dialect_table_class = Table.create(
|
|
81
|
+
"new-dialect") # assuming you've registered "new-dialect" with the `QueryRegistry`
|
|
82
|
+
table = new_dialect_table_class("test_table", "test_field", "another_test_field")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
You can overwrite the original fields supplied to a `Table` as well, which will delete the old attributes and set new ones:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from pysqlscribe.table import MySQLTable
|
|
89
|
+
|
|
90
|
+
table = MySQLTable("test_table", "test_field", "another_test_field")
|
|
91
|
+
table.test_field # valid
|
|
92
|
+
table.fields = ['new_test_field']
|
|
93
|
+
table.select("new_test_field")
|
|
94
|
+
table.new_test_field # now valid - but `table.test_field` is not anymore
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Schema
|
|
101
|
+
For associating multiple `Table`s with a single schema, you can use the `Schema`:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from pysqlscribe.schema import Schema
|
|
105
|
+
|
|
106
|
+
schema = Schema("test_schema", tables=["test_table", "another_test_table"], dialect="postgres")
|
|
107
|
+
schema.tables # a list of two `Table` objects
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This is functionally equivalent to:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from pysqlscribe.table import PostgresTable
|
|
114
|
+
|
|
115
|
+
table = PostgresTable("test_table", schema="test_schema")
|
|
116
|
+
another_table = PostgresTable("another_test_table", schema="test_schema")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Instead of supplying a `dialect` directly to `Schema`, you can also set the environment variable `PYQUERY_BUILDER_DIALECT`:
|
|
120
|
+
|
|
121
|
+
```shell
|
|
122
|
+
export PYQUERY_BUILDER_DIALECT = 'postgres'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pysqlscribe.schema import Schema
|
|
127
|
+
|
|
128
|
+
schema = Schema("test_schema", tables=["test_table", "another_test_table"])
|
|
129
|
+
schema.tables # a list of two `PostgresTable` objects
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Alternatively, if you already have existing `Table` objects you want to associate with the schema, you can supply them directly (in this case, `dialect` is not needed):
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from pysqlscribe.schema import Schema
|
|
136
|
+
from pysqlscribe.table import PostgresTable
|
|
137
|
+
|
|
138
|
+
table = PostgresTable("test_table")
|
|
139
|
+
another_table = PostgresTable("another_test_table")
|
|
140
|
+
schema = Schema("test_schema", [table, another_table])
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
`Schema` also has each table set as an attribute, so in the example above, you can do the following:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
schema.test_table # will return the supplied table object with the name `"test_table"`
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# Contributions
|
|
152
|
+
|
|
153
|
+
Contributions are always welcome, as this is a new and active project.
|
|
154
|
+
|
|
155
|
+
## Local Environment Setup
|
|
156
|
+
This project currently uses `uv` for convenience, although we currently only have dev dependencies. To create your environment:
|
|
157
|
+
```shell
|
|
158
|
+
uv sync
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Unit Testing
|
|
162
|
+
`pytest` is used for all unit testing. To run the unit tests locally (assuming a local environment is set up):
|
|
163
|
+
```shell
|
|
164
|
+
uv run pytest
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Unit tests are executed as part of CI, and the behavior should be consistent with what is observed in local development.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Supported Dialects
|
|
171
|
+
This is anticipated to grow, also there are certainly operations that are missing within dialects.
|
|
172
|
+
- [X] `MySQL`
|
|
173
|
+
- [X] `Oracle`
|
|
174
|
+
- [X] `Postgres`
|
|
175
|
+
- [X] `Sqlite`
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# TODO
|
|
179
|
+
- [ ] Support `JOIN`s
|
|
180
|
+
- [ ] Add more dialects
|
|
181
|
+
- [ ] Support `OFFSET` for Oracle and SQLServer
|
|
182
|
+
- [ ] Add a better abstraction around fields such that we can build expressions from comparisons, etc. as strings are limiting
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pysqlscribe"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "A simple Python Library for building relational database queries using objects"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name="Daniel Cahall", email="danielenricocahall@gmail.com" }]
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = []
|
|
9
|
+
license-files = ["LICENSE"]
|
|
10
|
+
|
|
11
|
+
[project.urls]
|
|
12
|
+
"Source code" = "https://github.com/danielenricocahall/pysqlscribe"
|
|
13
|
+
|
|
14
|
+
[tool.uv]
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"pre-commit>=4.1.0",
|
|
19
|
+
"pytest>=8.3.4",
|
|
20
|
+
"ruff>=0.9.2",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["hatchling"]
|
|
26
|
+
build-backend = "hatchling.build"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build]
|
|
29
|
+
exclude = [
|
|
30
|
+
"/.*",
|
|
31
|
+
"/tests",
|
|
32
|
+
"README.md",
|
|
33
|
+
|
|
34
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import operator
|
|
2
|
+
from abc import abstractmethod, ABC
|
|
3
|
+
from functools import reduce
|
|
4
|
+
from typing import Any, Dict, Self, Callable, Tuple
|
|
5
|
+
|
|
6
|
+
from pysqlscribe.regex_patterns import VALID_IDENTIFIER_REGEX
|
|
7
|
+
|
|
8
|
+
SELECT = "SELECT"
|
|
9
|
+
FROM = "FROM"
|
|
10
|
+
WHERE = "WHERE"
|
|
11
|
+
LIMIT = "LIMIT"
|
|
12
|
+
ORDER_BY = "ORDER BY"
|
|
13
|
+
AND = "AND"
|
|
14
|
+
FETCH_NEXT = "FETCH NEXT"
|
|
15
|
+
OFFSET = "OFFSET"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def reconcile_args_into_string(*args, escape_identifier: Callable[[str], str]) -> str:
|
|
19
|
+
arg = args[0]
|
|
20
|
+
if isinstance(arg, str):
|
|
21
|
+
arg = [arg]
|
|
22
|
+
for identifier in arg:
|
|
23
|
+
identifier = identifier.strip()
|
|
24
|
+
if not VALID_IDENTIFIER_REGEX.match(identifier):
|
|
25
|
+
raise ValueError(f"Invalid SQL identifier: {identifier}")
|
|
26
|
+
identifiers = [escape_identifier(identifier) for identifier in arg]
|
|
27
|
+
return ",".join(identifiers)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InvalidNodeException(Exception): ...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Node(ABC):
|
|
34
|
+
next_: Self | None = None
|
|
35
|
+
prev_: Self | None = None
|
|
36
|
+
state: dict[str, Any]
|
|
37
|
+
|
|
38
|
+
def __init__(self, state):
|
|
39
|
+
self.state = state
|
|
40
|
+
|
|
41
|
+
def add(self, next_: "Node"):
|
|
42
|
+
if not isinstance(next_, self.valid_next_nodes):
|
|
43
|
+
raise InvalidNodeException()
|
|
44
|
+
next_.prev_ = self
|
|
45
|
+
self.next_ = next_
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def valid_next_nodes(self) -> Tuple[type[Self], ...]: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SelectNode(Node):
|
|
53
|
+
@property
|
|
54
|
+
def valid_next_nodes(self):
|
|
55
|
+
return (FromNode,)
|
|
56
|
+
|
|
57
|
+
def __str__(self):
|
|
58
|
+
return f"{SELECT} {self.state['fields']}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class FromNode(Node):
|
|
62
|
+
@property
|
|
63
|
+
def valid_next_nodes(self):
|
|
64
|
+
return WhereNode, GroupByNode, OrderByNode, LimitNode
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return f"{FROM} {self.state['tables']}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class WhereNode(Node):
|
|
71
|
+
@property
|
|
72
|
+
def valid_next_nodes(self):
|
|
73
|
+
return GroupByNode, OrderByNode, LimitNode, WhereNode
|
|
74
|
+
|
|
75
|
+
def __str__(self):
|
|
76
|
+
return f"{WHERE} {self.state['conditions']}"
|
|
77
|
+
|
|
78
|
+
def __and__(self, other):
|
|
79
|
+
if isinstance(other, WhereNode):
|
|
80
|
+
compound_condition = (
|
|
81
|
+
f"{self.state['conditions']} {AND} {other.state['conditions']}"
|
|
82
|
+
)
|
|
83
|
+
return WhereNode({"conditions": compound_condition})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class GroupByNode(Node):
|
|
87
|
+
@property
|
|
88
|
+
def valid_next_nodes(self):
|
|
89
|
+
return OrderByNode
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class OrderByNode(Node):
|
|
93
|
+
@property
|
|
94
|
+
def valid_next_nodes(self):
|
|
95
|
+
return LimitNode
|
|
96
|
+
|
|
97
|
+
def __str__(self):
|
|
98
|
+
return f"{ORDER_BY} {self.state['fields']}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LimitNode(Node):
|
|
102
|
+
@property
|
|
103
|
+
def valid_next_nodes(self):
|
|
104
|
+
return OffsetNode
|
|
105
|
+
|
|
106
|
+
def __str__(self):
|
|
107
|
+
return f"{LIMIT} {self.state['limit']}"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class FetchNextNode(LimitNode):
|
|
111
|
+
@property
|
|
112
|
+
def valid_next_nodes(self):
|
|
113
|
+
return ()
|
|
114
|
+
|
|
115
|
+
def __str__(self):
|
|
116
|
+
return f"{FETCH_NEXT} {self.state['limit']} ROWS ONLY"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class OffsetNode(Node):
|
|
120
|
+
@property
|
|
121
|
+
def valid_next_nodes(self):
|
|
122
|
+
return ()
|
|
123
|
+
|
|
124
|
+
def __str__(self):
|
|
125
|
+
return f"{OFFSET} {self.state['offset']}"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class Query(ABC):
|
|
129
|
+
node: Node | None = None
|
|
130
|
+
|
|
131
|
+
def select(self, *args) -> Self:
|
|
132
|
+
if not self.node:
|
|
133
|
+
self.node = SelectNode(
|
|
134
|
+
{
|
|
135
|
+
"fields": reconcile_args_into_string(
|
|
136
|
+
args, escape_identifier=self.escape_identifier
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def from_(self, *args) -> Self:
|
|
143
|
+
self.node.add(
|
|
144
|
+
FromNode(
|
|
145
|
+
{
|
|
146
|
+
"tables": reconcile_args_into_string(
|
|
147
|
+
args, escape_identifier=self.escape_identifier
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
self.node = self.node.next_
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
def where(self, *args) -> Self:
|
|
156
|
+
where_node = reduce(
|
|
157
|
+
operator.and_, map(lambda arg: WhereNode({"conditions": arg}), args)
|
|
158
|
+
)
|
|
159
|
+
self.node.add(where_node)
|
|
160
|
+
self.node = self.node.next_
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
def order_by(self, *args) -> Self:
|
|
164
|
+
self.node.add(
|
|
165
|
+
OrderByNode(
|
|
166
|
+
{
|
|
167
|
+
"fields": reconcile_args_into_string(
|
|
168
|
+
args, escape_identifier=self.escape_identifier
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
self.node = self.node.next_
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
def limit(self, n: int | str):
|
|
177
|
+
self.node.add(LimitNode({"limit": int(n)}))
|
|
178
|
+
self.node = self.node = self.node.next_
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def offset(self, n: int | str):
|
|
182
|
+
self.node.add(OffsetNode({"offset": int(n)}))
|
|
183
|
+
self.node = self.node = self.node.next_
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def build(self, clear: bool = True) -> str:
|
|
187
|
+
node = self.node
|
|
188
|
+
query = ""
|
|
189
|
+
while True:
|
|
190
|
+
query = str(node) + " " + query
|
|
191
|
+
node = node.prev_
|
|
192
|
+
if node is None:
|
|
193
|
+
break
|
|
194
|
+
if clear:
|
|
195
|
+
# we provide an option to not clear the builder in the event the developer needs
|
|
196
|
+
# to debug or needs to reuse the value. By default, we do immediately after building the query
|
|
197
|
+
self.node = None
|
|
198
|
+
return query.strip()
|
|
199
|
+
|
|
200
|
+
def __str__(self):
|
|
201
|
+
return self.build(clear=False)
|
|
202
|
+
|
|
203
|
+
@abstractmethod
|
|
204
|
+
def escape_identifier(self, identifier: str): ...
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class QueryRegistry:
|
|
208
|
+
builders: Dict[str, Query] = {}
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def register(cls, key: str):
|
|
212
|
+
def decorator(builder_class: Callable[[], Query]) -> Callable[[], Query]:
|
|
213
|
+
cls.builders[key] = builder_class()
|
|
214
|
+
return builder_class
|
|
215
|
+
|
|
216
|
+
return decorator
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def get_builder(cls, key: str) -> Query:
|
|
220
|
+
return cls.builders[key]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@QueryRegistry.register("mysql")
|
|
224
|
+
class MySQLQuery(Query):
|
|
225
|
+
def escape_identifier(self, identifier: str) -> str:
|
|
226
|
+
return f"`{identifier}`"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@QueryRegistry.register("oracle")
|
|
230
|
+
class OracleQuery(Query):
|
|
231
|
+
def escape_identifier(self, identifier: str) -> str:
|
|
232
|
+
return f'"{identifier}"'
|
|
233
|
+
|
|
234
|
+
def limit(self, n: int | str):
|
|
235
|
+
self.node.add(FetchNextNode({"limit": int(n)}))
|
|
236
|
+
self.node = self.node = self.node.next_
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@QueryRegistry.register("postgres")
|
|
241
|
+
class PostgreSQLQuery(Query):
|
|
242
|
+
def escape_identifier(self, identifier: str) -> str:
|
|
243
|
+
return f'"{identifier}"'
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@QueryRegistry.register("sqlite")
|
|
247
|
+
class SQLiteQuery(Query):
|
|
248
|
+
def escape_identifier(self, identifier: str) -> str:
|
|
249
|
+
return f'"{identifier}"'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from pysqlscribe.regex_patterns import VALID_IDENTIFIER_REGEX
|
|
5
|
+
from pysqlscribe.table import Table
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvalidSchemaNameException(Exception): ...
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DialectNotSpecifiedException(Exception): ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Schema:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
name: str,
|
|
18
|
+
tables: List[Table | str] | None = None,
|
|
19
|
+
dialect: str | None = None,
|
|
20
|
+
):
|
|
21
|
+
self.name = name
|
|
22
|
+
self.dialect = dialect
|
|
23
|
+
self.tables = tables
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self):
|
|
27
|
+
return self._name
|
|
28
|
+
|
|
29
|
+
@name.setter
|
|
30
|
+
def name(self, schema_name: str):
|
|
31
|
+
if not VALID_IDENTIFIER_REGEX.match(schema_name):
|
|
32
|
+
raise InvalidSchemaNameException(f"Invalid schema name {schema_name}")
|
|
33
|
+
self._name = schema_name
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def tables(self):
|
|
37
|
+
return self._tables
|
|
38
|
+
|
|
39
|
+
@tables.setter
|
|
40
|
+
def tables(self, tables_: list[str | Table]):
|
|
41
|
+
if all(isinstance(table, str) for table in tables_):
|
|
42
|
+
tables_ = [Table.create(self.dialect)(table_name) for table_name in tables_]
|
|
43
|
+
for table in tables_:
|
|
44
|
+
setattr(self, table.name, table)
|
|
45
|
+
table.schema = self.name
|
|
46
|
+
self._tables = tables_
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def dialect(self):
|
|
50
|
+
return self._dialect
|
|
51
|
+
|
|
52
|
+
@dialect.setter
|
|
53
|
+
def dialect(self, dialect_: str | None):
|
|
54
|
+
if dialect_:
|
|
55
|
+
self._dialect = dialect_
|
|
56
|
+
elif env_set_dialect := os.environ.get("PYQUERY_BUILDER_DIALECT"):
|
|
57
|
+
self._dialect = env_set_dialect
|
|
58
|
+
else:
|
|
59
|
+
# the user may have provided no `dialect` - this is fine if they're directly supplying `Table` objects.
|
|
60
|
+
self._dialect = dialect_
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from pysqlscribe.query import QueryRegistry
|
|
5
|
+
from pysqlscribe.regex_patterns import VALID_IDENTIFIER_REGEX
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvalidFieldsException(Exception): ...
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InvalidTableNameException(Exception): ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Table(ABC):
|
|
15
|
+
__cache: dict[str, type["Table"]] = {}
|
|
16
|
+
|
|
17
|
+
def __init__(self, name: str, *fields, schema: str | None = None):
|
|
18
|
+
self.name = name
|
|
19
|
+
self.schema = schema
|
|
20
|
+
self.fields = fields
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def create(cls, dialect: str):
|
|
24
|
+
if dialect not in QueryRegistry.builders:
|
|
25
|
+
raise ValueError(f"Unsupported dialect: {dialect}")
|
|
26
|
+
|
|
27
|
+
class_name = f"{dialect.capitalize()}Table"
|
|
28
|
+
|
|
29
|
+
if class_name in cls.__cache:
|
|
30
|
+
return cls.__cache[class_name]
|
|
31
|
+
|
|
32
|
+
query_class = QueryRegistry.get_builder(dialect).__class__
|
|
33
|
+
|
|
34
|
+
class DynamicTable(query_class, Table):
|
|
35
|
+
def __init__(self, name: str, *fields, schema: str | None = None):
|
|
36
|
+
query_class.__init__(self)
|
|
37
|
+
Table.__init__(self, name, *fields, schema=schema)
|
|
38
|
+
|
|
39
|
+
def select(self, *fields):
|
|
40
|
+
try:
|
|
41
|
+
assert all(hasattr(self, field) for field in fields)
|
|
42
|
+
return super().select(*fields).from_(self.name)
|
|
43
|
+
except AssertionError:
|
|
44
|
+
raise InvalidFieldsException(
|
|
45
|
+
f"Table {self.name} doesn't have one or more of the fields provided"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Set a meaningful class name
|
|
49
|
+
DynamicTable.__name__ = class_name
|
|
50
|
+
cls.__cache[class_name] = DynamicTable
|
|
51
|
+
|
|
52
|
+
return DynamicTable
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def name(self):
|
|
56
|
+
if self.schema:
|
|
57
|
+
return f"{self.schema}.{self._name}"
|
|
58
|
+
return self._name
|
|
59
|
+
|
|
60
|
+
@name.setter
|
|
61
|
+
def name(self, table_name: str):
|
|
62
|
+
if not VALID_IDENTIFIER_REGEX.match(table_name):
|
|
63
|
+
raise InvalidTableNameException(f"Invalid table name {table_name}")
|
|
64
|
+
self._name = table_name
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def fields(self):
|
|
68
|
+
return self._fields
|
|
69
|
+
|
|
70
|
+
@fields.setter
|
|
71
|
+
def fields(self, fields_: List[str]):
|
|
72
|
+
if getattr(self, "_fields", None):
|
|
73
|
+
for field in self.fields:
|
|
74
|
+
delattr(self, field)
|
|
75
|
+
self._fields = fields_
|
|
76
|
+
for field in fields_:
|
|
77
|
+
setattr(self, field, None)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
MySQLTable = Table.create("mysql")
|
|
81
|
+
OracleTable = Table.create("oracle")
|
|
82
|
+
PostgresTable = Table.create("postgres")
|
|
83
|
+
SqliteTable = Table.create("sqlite")
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
requires-python = ">=3.10"
|
|
3
|
+
|
|
4
|
+
[[package]]
|
|
5
|
+
name = "cfgv"
|
|
6
|
+
version = "3.4.0"
|
|
7
|
+
source = { registry = "https://pypi.org/simple" }
|
|
8
|
+
sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 }
|
|
9
|
+
wheels = [
|
|
10
|
+
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[[package]]
|
|
14
|
+
name = "colorama"
|
|
15
|
+
version = "0.4.6"
|
|
16
|
+
source = { registry = "https://pypi.org/simple" }
|
|
17
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
|
18
|
+
wheels = [
|
|
19
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[[package]]
|
|
23
|
+
name = "distlib"
|
|
24
|
+
version = "0.3.9"
|
|
25
|
+
source = { registry = "https://pypi.org/simple" }
|
|
26
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 }
|
|
27
|
+
wheels = [
|
|
28
|
+
{ url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[[package]]
|
|
32
|
+
name = "exceptiongroup"
|
|
33
|
+
version = "1.2.2"
|
|
34
|
+
source = { registry = "https://pypi.org/simple" }
|
|
35
|
+
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
|
|
36
|
+
wheels = [
|
|
37
|
+
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[[package]]
|
|
41
|
+
name = "filelock"
|
|
42
|
+
version = "3.17.0"
|
|
43
|
+
source = { registry = "https://pypi.org/simple" }
|
|
44
|
+
sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 }
|
|
45
|
+
wheels = [
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[[package]]
|
|
50
|
+
name = "identify"
|
|
51
|
+
version = "2.6.6"
|
|
52
|
+
source = { registry = "https://pypi.org/simple" }
|
|
53
|
+
sdist = { url = "https://files.pythonhosted.org/packages/82/bf/c68c46601bacd4c6fb4dd751a42b6e7087240eaabc6487f2ef7a48e0e8fc/identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251", size = 99217 }
|
|
54
|
+
wheels = [
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/74/a1/68a395c17eeefb04917034bd0a1bfa765e7654fa150cca473d669aa3afb5/identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881", size = 99083 },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[[package]]
|
|
59
|
+
name = "iniconfig"
|
|
60
|
+
version = "2.0.0"
|
|
61
|
+
source = { registry = "https://pypi.org/simple" }
|
|
62
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
|
|
63
|
+
wheels = [
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[[package]]
|
|
68
|
+
name = "nodeenv"
|
|
69
|
+
version = "1.9.1"
|
|
70
|
+
source = { registry = "https://pypi.org/simple" }
|
|
71
|
+
sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 }
|
|
72
|
+
wheels = [
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 },
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "packaging"
|
|
78
|
+
version = "24.2"
|
|
79
|
+
source = { registry = "https://pypi.org/simple" }
|
|
80
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
|
81
|
+
wheels = [
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "platformdirs"
|
|
87
|
+
version = "4.3.6"
|
|
88
|
+
source = { registry = "https://pypi.org/simple" }
|
|
89
|
+
sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 }
|
|
90
|
+
wheels = [
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[[package]]
|
|
95
|
+
name = "pluggy"
|
|
96
|
+
version = "1.5.0"
|
|
97
|
+
source = { registry = "https://pypi.org/simple" }
|
|
98
|
+
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
|
99
|
+
wheels = [
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[[package]]
|
|
104
|
+
name = "pre-commit"
|
|
105
|
+
version = "4.1.0"
|
|
106
|
+
source = { registry = "https://pypi.org/simple" }
|
|
107
|
+
dependencies = [
|
|
108
|
+
{ name = "cfgv" },
|
|
109
|
+
{ name = "identify" },
|
|
110
|
+
{ name = "nodeenv" },
|
|
111
|
+
{ name = "pyyaml" },
|
|
112
|
+
{ name = "virtualenv" },
|
|
113
|
+
]
|
|
114
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 }
|
|
115
|
+
wheels = [
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
[[package]]
|
|
120
|
+
name = "pysqlscribe"
|
|
121
|
+
version = "0.0.1"
|
|
122
|
+
source = { editable = "." }
|
|
123
|
+
|
|
124
|
+
[package.dev-dependencies]
|
|
125
|
+
dev = [
|
|
126
|
+
{ name = "pre-commit" },
|
|
127
|
+
{ name = "pytest" },
|
|
128
|
+
{ name = "ruff" },
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
[package.metadata]
|
|
132
|
+
|
|
133
|
+
[package.metadata.requires-dev]
|
|
134
|
+
dev = [
|
|
135
|
+
{ name = "pre-commit", specifier = ">=4.1.0" },
|
|
136
|
+
{ name = "pytest", specifier = ">=8.3.4" },
|
|
137
|
+
{ name = "ruff", specifier = ">=0.9.2" },
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
[[package]]
|
|
141
|
+
name = "pytest"
|
|
142
|
+
version = "8.3.4"
|
|
143
|
+
source = { registry = "https://pypi.org/simple" }
|
|
144
|
+
dependencies = [
|
|
145
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
146
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
147
|
+
{ name = "iniconfig" },
|
|
148
|
+
{ name = "packaging" },
|
|
149
|
+
{ name = "pluggy" },
|
|
150
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
151
|
+
]
|
|
152
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
|
|
153
|
+
wheels = [
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
[[package]]
|
|
158
|
+
name = "pyyaml"
|
|
159
|
+
version = "6.0.2"
|
|
160
|
+
source = { registry = "https://pypi.org/simple" }
|
|
161
|
+
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
|
|
162
|
+
wheels = [
|
|
163
|
+
{ url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 },
|
|
164
|
+
{ url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 },
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 },
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 },
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 },
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 },
|
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 },
|
|
175
|
+
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 },
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 },
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 },
|
|
178
|
+
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 },
|
|
179
|
+
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 },
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
|
|
183
|
+
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
|
|
184
|
+
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
|
|
198
|
+
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
[[package]]
|
|
202
|
+
name = "ruff"
|
|
203
|
+
version = "0.9.5"
|
|
204
|
+
source = { registry = "https://pypi.org/simple" }
|
|
205
|
+
sdist = { url = "https://files.pythonhosted.org/packages/02/74/6c359f6b9ed85b88df6ef31febce18faeb852f6c9855651dfb1184a46845/ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c", size = 3634177 }
|
|
206
|
+
wheels = [
|
|
207
|
+
{ url = "https://files.pythonhosted.org/packages/17/4b/82b7c9ac874e72b82b19fd7eab57d122e2df44d2478d90825854f9232d02/ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442", size = 11681264 },
|
|
208
|
+
{ url = "https://files.pythonhosted.org/packages/27/5c/f5ae0a9564e04108c132e1139d60491c0abc621397fe79a50b3dc0bd704b/ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a", size = 11657554 },
|
|
209
|
+
{ url = "https://files.pythonhosted.org/packages/2a/83/c6926fa3ccb97cdb3c438bb56a490b395770c750bf59f9bc1fe57ae88264/ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36", size = 11088959 },
|
|
210
|
+
{ url = "https://files.pythonhosted.org/packages/af/a7/42d1832b752fe969ffdbfcb1b4cb477cb271bed5835110fb0a16ef31ab81/ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001", size = 11902041 },
|
|
211
|
+
{ url = "https://files.pythonhosted.org/packages/53/cf/1fffa09fb518d646f560ccfba59f91b23c731e461d6a4dedd21a393a1ff1/ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b", size = 11421069 },
|
|
212
|
+
{ url = "https://files.pythonhosted.org/packages/09/27/bb8f1b7304e2a9431f631ae7eadc35550fe0cf620a2a6a0fc4aa3d736f94/ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070", size = 12625095 },
|
|
213
|
+
{ url = "https://files.pythonhosted.org/packages/d7/ce/ab00bc9d3df35a5f1b64f5117458160a009f93ae5caf65894ebb63a1842d/ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440", size = 13257797 },
|
|
214
|
+
{ url = "https://files.pythonhosted.org/packages/88/81/c639a082ae6d8392bc52256058ec60f493c6a4d06d5505bccface3767e61/ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80", size = 12763793 },
|
|
215
|
+
{ url = "https://files.pythonhosted.org/packages/b3/d0/0a3d8f56d1e49af466dc770eeec5c125977ba9479af92e484b5b0251ce9c/ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393", size = 14386234 },
|
|
216
|
+
{ url = "https://files.pythonhosted.org/packages/04/70/e59c192a3ad476355e7f45fb3a87326f5219cc7c472e6b040c6c6595c8f0/ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2", size = 12437505 },
|
|
217
|
+
{ url = "https://files.pythonhosted.org/packages/55/4e/3abba60a259d79c391713e7a6ccabf7e2c96e5e0a19100bc4204f1a43a51/ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee", size = 11884799 },
|
|
218
|
+
{ url = "https://files.pythonhosted.org/packages/a3/db/b0183a01a9f25b4efcae919c18fb41d32f985676c917008620ad692b9d5f/ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1", size = 11527411 },
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/0a/e4/3ebfcebca3dff1559a74c6becff76e0b64689cea02b7aab15b8b32ea245d/ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a", size = 12078868 },
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/ec/b2/5ab808833e06c0a1b0d046a51c06ec5687b73c78b116e8d77687dc0cd515/ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5", size = 12524374 },
|
|
221
|
+
{ url = "https://files.pythonhosted.org/packages/e0/51/1432afcc3b7aa6586c480142caae5323d59750925c3559688f2a9867343f/ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723", size = 9853682 },
|
|
222
|
+
{ url = "https://files.pythonhosted.org/packages/b7/ad/c7a900591bd152bb47fc4882a27654ea55c7973e6d5d6396298ad3fd6638/ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6", size = 10865744 },
|
|
223
|
+
{ url = "https://files.pythonhosted.org/packages/75/d9/fde7610abd53c0c76b6af72fc679cb377b27c617ba704e25da834e0a0608/ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9", size = 10064595 },
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
[[package]]
|
|
227
|
+
name = "tomli"
|
|
228
|
+
version = "2.2.1"
|
|
229
|
+
source = { registry = "https://pypi.org/simple" }
|
|
230
|
+
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
|
|
231
|
+
wheels = [
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
|
|
236
|
+
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
|
|
257
|
+
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
|
|
258
|
+
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
|
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
|
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
|
|
261
|
+
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
|
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
[[package]]
|
|
266
|
+
name = "virtualenv"
|
|
267
|
+
version = "20.29.1"
|
|
268
|
+
source = { registry = "https://pypi.org/simple" }
|
|
269
|
+
dependencies = [
|
|
270
|
+
{ name = "distlib" },
|
|
271
|
+
{ name = "filelock" },
|
|
272
|
+
{ name = "platformdirs" },
|
|
273
|
+
]
|
|
274
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a7/ca/f23dcb02e161a9bba141b1c08aa50e8da6ea25e6d780528f1d385a3efe25/virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35", size = 7658028 }
|
|
275
|
+
wheels = [
|
|
276
|
+
{ url = "https://files.pythonhosted.org/packages/89/9b/599bcfc7064fbe5740919e78c5df18e5dceb0887e676256a1061bb5ae232/virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779", size = 4282379 },
|
|
277
|
+
]
|