sqlalchemy-hawdb 0.22.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy_hawdb/__init__.py +5 -0
- sqlalchemy_hawdb/dialect.py +323 -0
- sqlalchemy_hawdb-0.22.0.dist-info/METADATA +130 -0
- sqlalchemy_hawdb-0.22.0.dist-info/RECORD +7 -0
- sqlalchemy_hawdb-0.22.0.dist-info/WHEEL +5 -0
- sqlalchemy_hawdb-0.22.0.dist-info/entry_points.txt +2 -0
- sqlalchemy_hawdb-0.22.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""SQLAlchemy dialect for HawDB's embedded SQL interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
|
|
7
|
+
from sqlalchemy.engine import default
|
|
8
|
+
from sqlalchemy.pool import NullPool, SingletonThreadPool
|
|
9
|
+
from sqlalchemy.sql.compiler import GenericTypeCompiler, IdentifierPreparer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HawDBIdentifierPreparer(IdentifierPreparer):
|
|
13
|
+
"""Quote identifiers using SQL-standard double quotes."""
|
|
14
|
+
|
|
15
|
+
reserved_words = IdentifierPreparer.reserved_words | {"match", "return", "graph"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HawDBTypeCompiler(GenericTypeCompiler):
|
|
19
|
+
"""Compile SQLAlchemy types to HawDB's supported PostgreSQL type subset."""
|
|
20
|
+
|
|
21
|
+
def visit_big_integer(self, type_, **kw):
|
|
22
|
+
return "BIGINT"
|
|
23
|
+
|
|
24
|
+
visit_integer = visit_big_integer
|
|
25
|
+
visit_small_integer = visit_big_integer
|
|
26
|
+
|
|
27
|
+
def visit_boolean(self, type_, **kw):
|
|
28
|
+
return "BOOLEAN"
|
|
29
|
+
|
|
30
|
+
def visit_double(self, type_, **kw):
|
|
31
|
+
return "DOUBLE PRECISION"
|
|
32
|
+
|
|
33
|
+
visit_float = visit_double
|
|
34
|
+
visit_real = visit_double
|
|
35
|
+
visit_numeric = visit_double
|
|
36
|
+
|
|
37
|
+
def visit_string(self, type_, **kw):
|
|
38
|
+
return "TEXT"
|
|
39
|
+
|
|
40
|
+
visit_text = visit_string
|
|
41
|
+
visit_unicode = visit_string
|
|
42
|
+
visit_unicode_text = visit_string
|
|
43
|
+
|
|
44
|
+
def visit_large_binary(self, type_, **kw):
|
|
45
|
+
return "BYTEA"
|
|
46
|
+
|
|
47
|
+
def visit_uuid(self, type_, **kw):
|
|
48
|
+
return "UUID"
|
|
49
|
+
|
|
50
|
+
def visit_date(self, type_, **kw):
|
|
51
|
+
return "TEXT"
|
|
52
|
+
|
|
53
|
+
visit_time = visit_date
|
|
54
|
+
visit_datetime = visit_date
|
|
55
|
+
|
|
56
|
+
def visit_enum(self, type_, **kw):
|
|
57
|
+
return "TEXT"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class HawDBDialect(default.DefaultDialect):
|
|
61
|
+
name = "hawdb"
|
|
62
|
+
driver = "embedded"
|
|
63
|
+
supports_statement_cache = True
|
|
64
|
+
supports_native_boolean = True
|
|
65
|
+
supports_native_uuid = True
|
|
66
|
+
supports_sane_rowcount = False
|
|
67
|
+
supports_sane_multi_rowcount = False
|
|
68
|
+
supports_sequences = False
|
|
69
|
+
supports_identity_columns = False
|
|
70
|
+
supports_default_values = False
|
|
71
|
+
supports_empty_insert = False
|
|
72
|
+
supports_alter = False
|
|
73
|
+
supports_comments = False
|
|
74
|
+
postfetch_lastrowid = False
|
|
75
|
+
default_paramstyle = "numeric_dollar"
|
|
76
|
+
preparer = HawDBIdentifierPreparer
|
|
77
|
+
type_compiler_cls = HawDBTypeCompiler
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def import_dbapi(cls):
|
|
81
|
+
try:
|
|
82
|
+
return importlib.import_module("hawdb.dbapi")
|
|
83
|
+
except ImportError as error:
|
|
84
|
+
raise ImportError(
|
|
85
|
+
"HawDB's Python binding is required; install it with "
|
|
86
|
+
"python -m pip install 'hawdb>=0.22.0,<0.23.0'"
|
|
87
|
+
) from error
|
|
88
|
+
|
|
89
|
+
def create_connect_args(self, url):
|
|
90
|
+
if url.username or url.password or url.host or url.port:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
"Invalid HawDB URL: provide a local database path without "
|
|
93
|
+
"credentials or a network host"
|
|
94
|
+
)
|
|
95
|
+
query = dict(url.query)
|
|
96
|
+
read_only = query.pop("read_only", "false").lower() in {"1", "true", "yes"}
|
|
97
|
+
if query:
|
|
98
|
+
options = ", ".join(sorted(query))
|
|
99
|
+
raise ValueError(f"unsupported HawDB URL option(s): {options}")
|
|
100
|
+
path = url.database
|
|
101
|
+
if path in (None, ":memory:"):
|
|
102
|
+
path = None
|
|
103
|
+
return (
|
|
104
|
+
[path],
|
|
105
|
+
{"read_only": read_only, "autocommit": False},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def do_begin(self, dbapi_connection):
|
|
109
|
+
# HawDB's PEP 249 connection begins on its first statement.
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def do_rollback(self, dbapi_connection):
|
|
113
|
+
dbapi_connection.rollback()
|
|
114
|
+
|
|
115
|
+
def do_commit(self, dbapi_connection):
|
|
116
|
+
dbapi_connection.commit()
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def get_pool_class(cls, url):
|
|
120
|
+
# File handles own their path and cannot be shared across threads.
|
|
121
|
+
# In-memory URLs need a per-thread handle to persist across checkouts.
|
|
122
|
+
if url.database in (None, ":memory:"):
|
|
123
|
+
return SingletonThreadPool
|
|
124
|
+
return NullPool
|
|
125
|
+
|
|
126
|
+
def get_server_version_info(self, connection):
|
|
127
|
+
import hawdb
|
|
128
|
+
|
|
129
|
+
return tuple(int(part) for part in hawdb.__version__.split(".")[:3])
|
|
130
|
+
|
|
131
|
+
def get_default_schema_name(self, connection):
|
|
132
|
+
return "public"
|
|
133
|
+
|
|
134
|
+
def get_schema_names(self, connection, **kw):
|
|
135
|
+
return ["public"]
|
|
136
|
+
|
|
137
|
+
def has_table(self, connection, table_name, schema=None, **kw):
|
|
138
|
+
if schema not in (None, "public"):
|
|
139
|
+
return False
|
|
140
|
+
result = connection.exec_driver_sql(
|
|
141
|
+
"SELECT table_name FROM information_schema.tables "
|
|
142
|
+
"WHERE table_schema = 'public' AND table_name = $1",
|
|
143
|
+
(table_name,),
|
|
144
|
+
)
|
|
145
|
+
return result.first() is not None
|
|
146
|
+
|
|
147
|
+
def get_table_names(self, connection, schema=None, **kw):
|
|
148
|
+
if schema not in (None, "public"):
|
|
149
|
+
return []
|
|
150
|
+
result = connection.exec_driver_sql(
|
|
151
|
+
"SELECT table_name FROM information_schema.tables "
|
|
152
|
+
"WHERE table_schema = 'public' AND table_type = 'BASE TABLE' "
|
|
153
|
+
"ORDER BY table_name"
|
|
154
|
+
)
|
|
155
|
+
return [row[0] for row in result]
|
|
156
|
+
|
|
157
|
+
def get_view_names(self, connection, schema=None, **kw):
|
|
158
|
+
if schema not in (None, "public"):
|
|
159
|
+
return []
|
|
160
|
+
result = connection.exec_driver_sql(
|
|
161
|
+
"SELECT table_name FROM information_schema.tables "
|
|
162
|
+
"WHERE table_schema = 'public' AND table_type = 'MATERIALIZED VIEW' "
|
|
163
|
+
"ORDER BY table_name"
|
|
164
|
+
)
|
|
165
|
+
return [row[0] for row in result]
|
|
166
|
+
|
|
167
|
+
def get_columns(self, connection, table_name, schema=None, **kw):
|
|
168
|
+
if schema not in (None, "public"):
|
|
169
|
+
return []
|
|
170
|
+
result = connection.exec_driver_sql(
|
|
171
|
+
"SELECT column_name, data_type, is_nullable, column_default "
|
|
172
|
+
"FROM information_schema.columns "
|
|
173
|
+
"WHERE table_schema = 'public' AND table_name = $1 "
|
|
174
|
+
"ORDER BY ordinal_position",
|
|
175
|
+
(table_name,),
|
|
176
|
+
)
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
"name": row[0],
|
|
180
|
+
"type": self._type_from_name(row[1]),
|
|
181
|
+
"nullable": row[2] == "YES",
|
|
182
|
+
"default": row[3],
|
|
183
|
+
"autoincrement": False,
|
|
184
|
+
"comment": None,
|
|
185
|
+
}
|
|
186
|
+
for row in result
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
|
190
|
+
if schema not in (None, "public"):
|
|
191
|
+
return {"name": None, "constrained_columns": []}
|
|
192
|
+
result = connection.exec_driver_sql(
|
|
193
|
+
"SELECT indexname, indexdef FROM pg_catalog.pg_indexes "
|
|
194
|
+
"WHERE schemaname = 'public' AND tablename = $1 ORDER BY indexname",
|
|
195
|
+
(table_name,),
|
|
196
|
+
)
|
|
197
|
+
primary_key_name = f"{table_name}_pkey"
|
|
198
|
+
for row in result:
|
|
199
|
+
if row[0] == primary_key_name:
|
|
200
|
+
return {
|
|
201
|
+
"name": row[0],
|
|
202
|
+
"constrained_columns": self._index_columns(row[1]),
|
|
203
|
+
}
|
|
204
|
+
return {"name": None, "constrained_columns": []}
|
|
205
|
+
|
|
206
|
+
def get_indexes(self, connection, table_name, schema=None, **kw):
|
|
207
|
+
if schema not in (None, "public"):
|
|
208
|
+
return []
|
|
209
|
+
result = connection.exec_driver_sql(
|
|
210
|
+
"SELECT indexname, indexdef FROM pg_catalog.pg_indexes "
|
|
211
|
+
"WHERE schemaname = 'public' AND tablename = $1 ORDER BY indexname",
|
|
212
|
+
(table_name,),
|
|
213
|
+
)
|
|
214
|
+
primary_key_name = f"{table_name}_pkey"
|
|
215
|
+
return [
|
|
216
|
+
{
|
|
217
|
+
"name": row[0],
|
|
218
|
+
"column_names": self._index_columns(row[1]),
|
|
219
|
+
"unique": row[1].upper().startswith("CREATE UNIQUE INDEX"),
|
|
220
|
+
"dialect_options": {},
|
|
221
|
+
}
|
|
222
|
+
for row in result
|
|
223
|
+
if row[0] != primary_key_name
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
def get_unique_constraints(self, connection, table_name, schema=None, **kw):
|
|
227
|
+
if schema not in (None, "public"):
|
|
228
|
+
return []
|
|
229
|
+
result = connection.exec_driver_sql(
|
|
230
|
+
"SELECT indexname, indexdef FROM pg_catalog.pg_indexes "
|
|
231
|
+
"WHERE schemaname = 'public' AND tablename = $1 ORDER BY indexname",
|
|
232
|
+
(table_name,),
|
|
233
|
+
)
|
|
234
|
+
primary_key_name = f"{table_name}_pkey"
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
"name": row[0],
|
|
238
|
+
"column_names": self._index_columns(row[1]),
|
|
239
|
+
"duplicates_index": row[0],
|
|
240
|
+
}
|
|
241
|
+
for row in result
|
|
242
|
+
if row[0] != primary_key_name
|
|
243
|
+
and row[1].upper().startswith("CREATE UNIQUE INDEX")
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def _index_columns(definition: str) -> list[str]:
|
|
248
|
+
"""Parse the simple parenthesized column list emitted by pg_indexes."""
|
|
249
|
+
opening = closing = -1
|
|
250
|
+
quoted = False
|
|
251
|
+
depth = 0
|
|
252
|
+
index = 0
|
|
253
|
+
while index < len(definition):
|
|
254
|
+
char = definition[index]
|
|
255
|
+
if char == '"':
|
|
256
|
+
if (
|
|
257
|
+
quoted
|
|
258
|
+
and index + 1 < len(definition)
|
|
259
|
+
and definition[index + 1] == '"'
|
|
260
|
+
):
|
|
261
|
+
index += 1
|
|
262
|
+
else:
|
|
263
|
+
quoted = not quoted
|
|
264
|
+
elif not quoted and char == "(":
|
|
265
|
+
if depth == 0:
|
|
266
|
+
opening = index
|
|
267
|
+
depth += 1
|
|
268
|
+
elif not quoted and char == ")" and depth:
|
|
269
|
+
depth -= 1
|
|
270
|
+
if depth == 0:
|
|
271
|
+
closing = index
|
|
272
|
+
index += 1
|
|
273
|
+
if opening < 0 or closing <= opening:
|
|
274
|
+
return []
|
|
275
|
+
columns = definition[opening + 1 : closing]
|
|
276
|
+
result = []
|
|
277
|
+
current = []
|
|
278
|
+
quoted = False
|
|
279
|
+
index = 0
|
|
280
|
+
while index < len(columns):
|
|
281
|
+
char = columns[index]
|
|
282
|
+
if char == '"':
|
|
283
|
+
if quoted and index + 1 < len(columns) and columns[index + 1] == '"':
|
|
284
|
+
current.append('"')
|
|
285
|
+
index += 1
|
|
286
|
+
else:
|
|
287
|
+
quoted = not quoted
|
|
288
|
+
elif char == "," and not quoted:
|
|
289
|
+
result.append("".join(current).strip())
|
|
290
|
+
current = []
|
|
291
|
+
else:
|
|
292
|
+
current.append(char)
|
|
293
|
+
index += 1
|
|
294
|
+
if current:
|
|
295
|
+
result.append("".join(current).strip())
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def _type_from_name(name: str):
|
|
300
|
+
from sqlalchemy import types
|
|
301
|
+
|
|
302
|
+
normalized = name.lower()
|
|
303
|
+
if normalized in {"smallint", "integer", "bigint"}:
|
|
304
|
+
return types.Integer()
|
|
305
|
+
if normalized in {"real", "double precision", "numeric", "decimal"}:
|
|
306
|
+
return types.Float()
|
|
307
|
+
if normalized in {"boolean", "bool"}:
|
|
308
|
+
return types.Boolean()
|
|
309
|
+
if normalized in {
|
|
310
|
+
"date",
|
|
311
|
+
"time",
|
|
312
|
+
"timestamp",
|
|
313
|
+
"timestamp without time zone",
|
|
314
|
+
"timestamp with time zone",
|
|
315
|
+
}:
|
|
316
|
+
return types.Text()
|
|
317
|
+
if normalized in {"uuid"}:
|
|
318
|
+
return types.Uuid()
|
|
319
|
+
if normalized in {"bytea", "binary", "varbinary"}:
|
|
320
|
+
return types.LargeBinary()
|
|
321
|
+
if normalized in {"text", "character varying", "varchar", "character", "char"}:
|
|
322
|
+
return types.Text()
|
|
323
|
+
return types.NullType()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlalchemy-hawdb
|
|
3
|
+
Version: 0.22.0
|
|
4
|
+
Summary: SQLAlchemy dialect for the embedded HawDB database
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Topic :: Database
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: SQLAlchemy<3.0,>=2.0
|
|
16
|
+
Requires-Dist: hawdb<0.23.0,>=0.22.0
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest<9,>=8; extra == "test"
|
|
19
|
+
Requires-Dist: ruff<1,>=0.14; extra == "test"
|
|
20
|
+
|
|
21
|
+
# SQLAlchemy HawDB
|
|
22
|
+
|
|
23
|
+
`sqlalchemy-hawdb` provides a SQLAlchemy 2.x dialect for the embedded HawDB
|
|
24
|
+
database. It uses HawDB's own PEP 249 driver and does not add a wire protocol or
|
|
25
|
+
duplicate its DBAPI implementation.
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Python 3.11 or newer
|
|
30
|
+
- HawDB Python binding 0.22.x (CI tests 0.22.0)
|
|
31
|
+
- SQLAlchemy 2.x
|
|
32
|
+
|
|
33
|
+
Install the dialect and its HawDB dependency from PyPI:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
python -m pip install sqlalchemy-hawdb
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
To install this checkout instead:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
python -m pip install /path/to/sqlalchemy_hawdb
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Connect
|
|
46
|
+
|
|
47
|
+
Use an absolute database directory path with four slashes:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from sqlalchemy import Column, Integer, MetaData, String, Table, create_engine
|
|
51
|
+
|
|
52
|
+
engine = create_engine("hawdb:////var/lib/my-app/knowledge")
|
|
53
|
+
metadata = MetaData()
|
|
54
|
+
records = Table(
|
|
55
|
+
"records",
|
|
56
|
+
metadata,
|
|
57
|
+
Column("id", Integer, primary_key=True, autoincrement=False),
|
|
58
|
+
Column("body", String(500), nullable=False),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
metadata.create_all(engine)
|
|
62
|
+
with engine.begin() as connection:
|
|
63
|
+
connection.execute(records.insert(), {"id": 1, "body": "Hello HawDB"})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
HawDB creates the database directory itself; its parent directory must already
|
|
67
|
+
exist.
|
|
68
|
+
|
|
69
|
+
Use `hawdb:///:memory:` for an in-memory database. Like SQLAlchemy's SQLite
|
|
70
|
+
dialect, the in-memory engine keeps one database per thread. Add
|
|
71
|
+
`?read_only=true` to open an existing database read-only.
|
|
72
|
+
|
|
73
|
+
HawDB connections are thread-affine and hold their database path until closed.
|
|
74
|
+
For file databases the dialect uses SQLAlchemy's `NullPool`: each SQLAlchemy
|
|
75
|
+
connection closes its HawDB handle when returned. Do not keep multiple
|
|
76
|
+
connections to the same database path open at once or pass a SQLAlchemy
|
|
77
|
+
connection between threads. In-memory connections are kept per thread and must
|
|
78
|
+
also be used serially within that thread.
|
|
79
|
+
|
|
80
|
+
## Supported behavior
|
|
81
|
+
|
|
82
|
+
- SQLAlchemy Core and ORM statements using HawDB's relational SQL subset
|
|
83
|
+
- Positional `$1`, `$2`, ... binding through HawDB's native DBAPI driver
|
|
84
|
+
- Explicit SQLAlchemy transactions mapped to HawDB commit and rollback
|
|
85
|
+
- Table, column, primary key, unique constraint, and index reflection from
|
|
86
|
+
HawDB's `information_schema` and `pg_catalog` views
|
|
87
|
+
- The default `public` schema
|
|
88
|
+
- HawDB-native scalar DDL types: integer types map to `BIGINT`, floating and
|
|
89
|
+
numeric types to `DOUBLE PRECISION`, strings to `TEXT`, and binary values to
|
|
90
|
+
`BYTEA`; SQLAlchemy string lengths and numeric precision are not enforced by
|
|
91
|
+
HawDB
|
|
92
|
+
|
|
93
|
+
HawDB does not report affected row counts, so DBAPI `rowcount` is `-1` for
|
|
94
|
+
statements without a result set. Date and time types compile to `TEXT`; their
|
|
95
|
+
values are encoded and decoded by SQLAlchemy's standard processors.
|
|
96
|
+
Auto-generated identity columns, sequences, schema alteration, savepoints, and
|
|
97
|
+
concurrent connections to one database path are not supported by this initial
|
|
98
|
+
dialect.
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
Install the project, the HawDB dependency, and the test tools:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
python -m pip install -e '.[test]'
|
|
106
|
+
python -m pytest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
GitHub Actions builds the source distribution and wheel, checks their metadata,
|
|
110
|
+
and tests the installed wheel against `hawdb==0.22.0` from PyPI on Python
|
|
111
|
+
3.11-3.13 for every push and pull request. Missing HawDB wheels fail CI rather
|
|
112
|
+
than skipping integration tests or falling back to a source build.
|
|
113
|
+
|
|
114
|
+
## Publishing
|
|
115
|
+
|
|
116
|
+
`.github/workflows/pypi.yaml` publishes the tested source distribution and wheel
|
|
117
|
+
when a GitHub Release is published. The release tag must match the version in
|
|
118
|
+
`pyproject.toml`, for example `v0.22.0`. A manual workflow dispatch runs the same
|
|
119
|
+
build and tests without uploading to PyPI.
|
|
120
|
+
|
|
121
|
+
Configure a PyPI Trusted Publisher for the `sqlalchemy-hawdb` project with:
|
|
122
|
+
|
|
123
|
+
- Owner: `nowledge-co`
|
|
124
|
+
- Repository: `sqlalchemy-hawdb`
|
|
125
|
+
- Workflow: `pypi.yaml`
|
|
126
|
+
- Environment: `pypi`
|
|
127
|
+
|
|
128
|
+
For the first release, configure a pending publisher in your PyPI account.
|
|
129
|
+
The publish job uses GitHub's `pypi` environment and OIDC; no PyPI API token
|
|
130
|
+
secret is required.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
sqlalchemy_hawdb/__init__.py,sha256=9zJu4JP1VSSpilWq0NlddfVQLpXG7bEHHeILcbgffjM,119
|
|
2
|
+
sqlalchemy_hawdb/dialect.py,sha256=uYb_OYDxmyYif51vUmXP1hQVzgrzl0-Db6a07kfb_0U,10996
|
|
3
|
+
sqlalchemy_hawdb-0.22.0.dist-info/METADATA,sha256=WMUsSECpUEskBw7YDxsPQqLFsJp3F2imHhXhzIVVfCs,4647
|
|
4
|
+
sqlalchemy_hawdb-0.22.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
5
|
+
sqlalchemy_hawdb-0.22.0.dist-info/entry_points.txt,sha256=O6N7Zc7UOCf5nMrTDrAZBCwGI04yEvxSV4LKVUBsa6c,68
|
|
6
|
+
sqlalchemy_hawdb-0.22.0.dist-info/top_level.txt,sha256=DSRzcmNpZj4SJkt_sDro6bAc0WOYZTIU2JW3jprKViU,17
|
|
7
|
+
sqlalchemy_hawdb-0.22.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sqlalchemy_hawdb
|