sqlalchemy-firebird-async 0.1.0__tar.gz → 0.2.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.
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/.gitignore +1 -0
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/PKG-INFO +26 -22
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/README.md +22 -21
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/pyproject.toml +5 -3
- sqlalchemy_firebird_async-0.2.1/src/sqlalchemy_firebird_async/compiler.py +12 -0
- sqlalchemy_firebird_async-0.2.1/src/sqlalchemy_firebird_async/fdb.py +160 -0
- sqlalchemy_firebird_async-0.2.1/src/sqlalchemy_firebird_async/firebird_driver.py +138 -0
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/firebirdsql.py +53 -47
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/tests/conftest.py +8 -7
- sqlalchemy_firebird_async-0.2.1/tests/test_basic.py +80 -0
- sqlalchemy_firebird_async-0.2.1/tests/test_load.py +131 -0
- sqlalchemy_firebird_async-0.2.1/tests/test_terminate.py +59 -0
- sqlalchemy_firebird_async-0.2.1/tests/test_types.py +141 -0
- sqlalchemy_firebird_async-0.1.0/check_query.py +0 -22
- sqlalchemy_firebird_async-0.1.0/check_simple.py +0 -12
- sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/fdb.py +0 -115
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/PKG-INFO +0 -50
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/README.md +0 -26
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/pyproject.toml +0 -39
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/__init__.py +0 -0
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/fdb.py +0 -115
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/firebirdsql.py +0 -154
- sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/uv.lock +0 -204
- sqlalchemy_firebird_async-0.1.0/temp_check/test_install/check.py +0 -29
- sqlalchemy_firebird_async-0.1.0/temp_check/test_install/db/sql_models.py +0 -140
- sqlalchemy_firebird_async-0.1.0/temp_check/test_install/main.py +0 -175
- sqlalchemy_firebird_async-0.1.0/tests/test_basic.py +0 -39
- sqlalchemy_firebird_async-0.1.0/tests/test_load.py +0 -79
- sqlalchemy_firebird_async-0.1.0/uv.lock +0 -468
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/LICENSE +0 -0
- {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlalchemy-firebird-async
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Asyncio support for Firebird in SQLAlchemy
|
|
5
5
|
Project-URL: Homepage, https://github.com/attid/sqlalchemy-firebird-async
|
|
6
6
|
Author-email: Igor Tolstov <attid0@gmail.com>
|
|
@@ -16,9 +16,12 @@ Requires-Dist: sqlalchemy-firebird>=2.0.0
|
|
|
16
16
|
Requires-Dist: sqlalchemy>=2.0.0
|
|
17
17
|
Provides-Extra: all
|
|
18
18
|
Requires-Dist: fdb>=2.0; extra == 'all'
|
|
19
|
+
Requires-Dist: firebird-driver>=1.0; extra == 'all'
|
|
19
20
|
Requires-Dist: firebirdsql>=1.0; extra == 'all'
|
|
20
21
|
Provides-Extra: fdb
|
|
21
22
|
Requires-Dist: fdb>=2.0; extra == 'fdb'
|
|
23
|
+
Provides-Extra: firebird-driver
|
|
24
|
+
Requires-Dist: firebird-driver>=1.0; extra == 'firebird-driver'
|
|
22
25
|
Provides-Extra: firebirdsql
|
|
23
26
|
Requires-Dist: firebirdsql>=1.0; extra == 'firebirdsql'
|
|
24
27
|
Provides-Extra: test
|
|
@@ -37,18 +40,22 @@ Description-Content-Type: text/markdown
|
|
|
37
40
|
|
|
38
41
|
This library provides proper `asyncio` support for Firebird databases in SQLAlchemy 2.0+, allowing you to write fully asynchronous code using modern Python patterns.
|
|
39
42
|
|
|
40
|
-
It supports
|
|
41
|
-
1. **`fdb`** (
|
|
42
|
-
2. **`
|
|
43
|
+
It supports three underlying drivers:
|
|
44
|
+
1. **`fdb`** (Legacy) - Runs the official C-based driver in a thread pool. Fast and stable.
|
|
45
|
+
2. **`firebird-driver`** (Modern) - Official driver for Firebird 3+. Also threaded (run_in_executor).
|
|
46
|
+
3. **`firebirdsql`** - Pure Python asyncio driver. Currently experimental due to upstream issues.
|
|
43
47
|
|
|
44
48
|
## 📦 Installation
|
|
45
49
|
|
|
46
50
|
Install using pip:
|
|
47
51
|
|
|
48
52
|
```bash
|
|
49
|
-
#
|
|
53
|
+
# Install with the FDB driver (Legacy, Stable)
|
|
50
54
|
pip install "sqlalchemy-firebird-async[fdb]"
|
|
51
55
|
|
|
56
|
+
# Install with the Firebird Driver (Modern, FB 3.0+)
|
|
57
|
+
pip install "sqlalchemy-firebird-async[firebird-driver]"
|
|
58
|
+
|
|
52
59
|
# Install with pure python driver (Experimental)
|
|
53
60
|
pip install "sqlalchemy-firebird-async[firebirdsql]"
|
|
54
61
|
# Note: For correct async behavior with firebirdsql, you might need a patched version:
|
|
@@ -57,9 +64,9 @@ pip install "sqlalchemy-firebird-async[firebirdsql]"
|
|
|
57
64
|
|
|
58
65
|
## 🚀 Quick Start
|
|
59
66
|
|
|
60
|
-
### 1. Using FDB Driver (
|
|
67
|
+
### 1. Using FDB Driver (Legacy)
|
|
61
68
|
|
|
62
|
-
This dialect runs the
|
|
69
|
+
This dialect runs the legacy `fdb` driver in a thread pool.
|
|
63
70
|
|
|
64
71
|
**URL Scheme:** `firebird+fdb_async://`
|
|
65
72
|
|
|
@@ -70,29 +77,25 @@ from sqlalchemy import text
|
|
|
70
77
|
|
|
71
78
|
async def main():
|
|
72
79
|
# Format: firebird+fdb_async://user:password@host:port/path/to/db
|
|
73
|
-
# Note: For Linux, ensure the path is absolute (e.g. //firebird/data/...)
|
|
74
80
|
dsn = "firebird+fdb_async://sysdba:masterkey@localhost:3050//firebird/data/employee.fdb"
|
|
75
81
|
|
|
76
82
|
engine = create_async_engine(dsn, echo=True)
|
|
83
|
+
# ... usage is identical for all drivers
|
|
84
|
+
```
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
result = await conn.execute(text("SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') FROM rdb$database"))
|
|
80
|
-
version = result.scalar()
|
|
81
|
-
print(f"Firebird Version: {version}")
|
|
86
|
+
### 2. Using Firebird Driver (Modern)
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
|
85
|
-
async with async_session() as session:
|
|
86
|
-
result = await session.execute(text("SELECT count(*) FROM rdb$relations"))
|
|
87
|
-
print(f"Total tables: {result.scalar()}")
|
|
88
|
+
This dialect uses the modern `firebird-driver` package (the official driver for Firebird 3.0+), running in a thread pool. It requires Firebird 3.0 or higher.
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
**URL Scheme:** `firebird+firebird_async://`
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
```python
|
|
93
|
+
engine = create_async_engine(
|
|
94
|
+
"firebird+firebird_async://sysdba:masterkey@localhost:3050//firebird/data/employee.fdb?charset=UTF8"
|
|
95
|
+
)
|
|
93
96
|
```
|
|
94
97
|
|
|
95
|
-
###
|
|
98
|
+
### 3. Using Native Async Driver (firebirdsql)
|
|
96
99
|
|
|
97
100
|
**Warning:** The upstream `firebirdsql` driver currently has issues with `asyncio` compatibility (bugs causing crashes or incorrect behavior).
|
|
98
101
|
A patched fork is available at [attid/pyfirebirdsql](https://github.com/attid/pyfirebirdsql.git), which fixes the async logic but currently exhibits significantly lower performance (approx. 4x slower than fdb).
|
|
@@ -124,7 +127,8 @@ As seen above, `fdb` in a thread pool is significantly faster for high-load scen
|
|
|
124
127
|
|
|
125
128
|
| Driver | Protocol | URL Scheme |
|
|
126
129
|
| :--- | :--- | :--- |
|
|
127
|
-
| **fdb** (
|
|
130
|
+
| **fdb** (Legacy) | TCP/IP | `firebird+fdb_async://user:pass@host:port/db_path` |
|
|
131
|
+
| **firebird-driver** (Modern) | TCP/IP | `firebird+firebird_async://user:pass@host:port/db_path` |
|
|
128
132
|
| **firebirdsql** | TCP/IP | `firebird+firebirdsql_async://user:pass@host:port/db_path` |
|
|
129
133
|
|
|
130
134
|
## 🤝 Contributing
|
|
@@ -8,18 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
This library provides proper `asyncio` support for Firebird databases in SQLAlchemy 2.0+, allowing you to write fully asynchronous code using modern Python patterns.
|
|
10
10
|
|
|
11
|
-
It supports
|
|
12
|
-
1. **`fdb`** (
|
|
13
|
-
2. **`
|
|
11
|
+
It supports three underlying drivers:
|
|
12
|
+
1. **`fdb`** (Legacy) - Runs the official C-based driver in a thread pool. Fast and stable.
|
|
13
|
+
2. **`firebird-driver`** (Modern) - Official driver for Firebird 3+. Also threaded (run_in_executor).
|
|
14
|
+
3. **`firebirdsql`** - Pure Python asyncio driver. Currently experimental due to upstream issues.
|
|
14
15
|
|
|
15
16
|
## 📦 Installation
|
|
16
17
|
|
|
17
18
|
Install using pip:
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
|
-
#
|
|
21
|
+
# Install with the FDB driver (Legacy, Stable)
|
|
21
22
|
pip install "sqlalchemy-firebird-async[fdb]"
|
|
22
23
|
|
|
24
|
+
# Install with the Firebird Driver (Modern, FB 3.0+)
|
|
25
|
+
pip install "sqlalchemy-firebird-async[firebird-driver]"
|
|
26
|
+
|
|
23
27
|
# Install with pure python driver (Experimental)
|
|
24
28
|
pip install "sqlalchemy-firebird-async[firebirdsql]"
|
|
25
29
|
# Note: For correct async behavior with firebirdsql, you might need a patched version:
|
|
@@ -28,9 +32,9 @@ pip install "sqlalchemy-firebird-async[firebirdsql]"
|
|
|
28
32
|
|
|
29
33
|
## 🚀 Quick Start
|
|
30
34
|
|
|
31
|
-
### 1. Using FDB Driver (
|
|
35
|
+
### 1. Using FDB Driver (Legacy)
|
|
32
36
|
|
|
33
|
-
This dialect runs the
|
|
37
|
+
This dialect runs the legacy `fdb` driver in a thread pool.
|
|
34
38
|
|
|
35
39
|
**URL Scheme:** `firebird+fdb_async://`
|
|
36
40
|
|
|
@@ -41,29 +45,25 @@ from sqlalchemy import text
|
|
|
41
45
|
|
|
42
46
|
async def main():
|
|
43
47
|
# Format: firebird+fdb_async://user:password@host:port/path/to/db
|
|
44
|
-
# Note: For Linux, ensure the path is absolute (e.g. //firebird/data/...)
|
|
45
48
|
dsn = "firebird+fdb_async://sysdba:masterkey@localhost:3050//firebird/data/employee.fdb"
|
|
46
49
|
|
|
47
50
|
engine = create_async_engine(dsn, echo=True)
|
|
51
|
+
# ... usage is identical for all drivers
|
|
52
|
+
```
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
result = await conn.execute(text("SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') FROM rdb$database"))
|
|
51
|
-
version = result.scalar()
|
|
52
|
-
print(f"Firebird Version: {version}")
|
|
54
|
+
### 2. Using Firebird Driver (Modern)
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
|
56
|
-
async with async_session() as session:
|
|
57
|
-
result = await session.execute(text("SELECT count(*) FROM rdb$relations"))
|
|
58
|
-
print(f"Total tables: {result.scalar()}")
|
|
56
|
+
This dialect uses the modern `firebird-driver` package (the official driver for Firebird 3.0+), running in a thread pool. It requires Firebird 3.0 or higher.
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
**URL Scheme:** `firebird+firebird_async://`
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
```python
|
|
61
|
+
engine = create_async_engine(
|
|
62
|
+
"firebird+firebird_async://sysdba:masterkey@localhost:3050//firebird/data/employee.fdb?charset=UTF8"
|
|
63
|
+
)
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### 3. Using Native Async Driver (firebirdsql)
|
|
67
67
|
|
|
68
68
|
**Warning:** The upstream `firebirdsql` driver currently has issues with `asyncio` compatibility (bugs causing crashes or incorrect behavior).
|
|
69
69
|
A patched fork is available at [attid/pyfirebirdsql](https://github.com/attid/pyfirebirdsql.git), which fixes the async logic but currently exhibits significantly lower performance (approx. 4x slower than fdb).
|
|
@@ -95,7 +95,8 @@ As seen above, `fdb` in a thread pool is significantly faster for high-load scen
|
|
|
95
95
|
|
|
96
96
|
| Driver | Protocol | URL Scheme |
|
|
97
97
|
| :--- | :--- | :--- |
|
|
98
|
-
| **fdb** (
|
|
98
|
+
| **fdb** (Legacy) | TCP/IP | `firebird+fdb_async://user:pass@host:port/db_path` |
|
|
99
|
+
| **firebird-driver** (Modern) | TCP/IP | `firebird+firebird_async://user:pass@host:port/db_path` |
|
|
99
100
|
| **firebirdsql** | TCP/IP | `firebird+firebirdsql_async://user:pass@host:port/db_path` |
|
|
100
101
|
|
|
101
102
|
## 🤝 Contributing
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlalchemy-firebird-async"
|
|
7
|
-
version = "0.1
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Asyncio support for Firebird in SQLAlchemy"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -27,7 +27,8 @@ dependencies = [
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
28
|
fdb = ["fdb>=2.0"]
|
|
29
29
|
firebirdsql = ["firebirdsql>=1.0"]
|
|
30
|
-
|
|
30
|
+
firebird-driver = ["firebird-driver>=1.0"]
|
|
31
|
+
all = ["fdb>=2.0", "firebirdsql>=1.0", "firebird-driver>=1.0"]
|
|
31
32
|
test = [
|
|
32
33
|
"pytest>=7.0",
|
|
33
34
|
"pytest-asyncio>=0.21",
|
|
@@ -37,6 +38,7 @@ test = [
|
|
|
37
38
|
[project.entry-points."sqlalchemy.dialects"]
|
|
38
39
|
"firebird.fdb_async" = "sqlalchemy_firebird_async.fdb:AsyncFDBDialect"
|
|
39
40
|
"firebird.firebirdsql_async" = "sqlalchemy_firebird_async.firebirdsql:AsyncFirebirdSQLDialect"
|
|
41
|
+
"firebird.firebird_async" = "sqlalchemy_firebird_async.firebird_driver:AsyncFirebirdDialect"
|
|
40
42
|
|
|
41
43
|
[project.urls]
|
|
42
|
-
"Homepage" = "https://github.com/attid/sqlalchemy-firebird-async"
|
|
44
|
+
"Homepage" = "https://github.com/attid/sqlalchemy-firebird-async"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from sqlalchemy_firebird.base import FBTypeCompiler
|
|
2
|
+
|
|
3
|
+
class PatchedFBTypeCompiler(FBTypeCompiler):
|
|
4
|
+
def _render_string_type(self, type_, name, length_override=None):
|
|
5
|
+
# Fix for TypeError: unsupported operand type(s) for +: 'int' and 'str'
|
|
6
|
+
if not isinstance(name, str):
|
|
7
|
+
# Attempt to restore type name from the type object itself
|
|
8
|
+
if hasattr(type_, "__visit_name__"):
|
|
9
|
+
name = type_.__visit_name__.upper()
|
|
10
|
+
else:
|
|
11
|
+
name = "VARCHAR"
|
|
12
|
+
return super()._render_string_type(type_, name, length_override)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import partial
|
|
3
|
+
from sqlalchemy.util.concurrency import await_only
|
|
4
|
+
from greenlet import getcurrent
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AsyncCursor:
|
|
8
|
+
def __init__(self, sync_cursor, loop):
|
|
9
|
+
self._sync_cursor = sync_cursor
|
|
10
|
+
self._loop = loop
|
|
11
|
+
|
|
12
|
+
def _exec(self, func, *args, **kwargs):
|
|
13
|
+
# Проверяем, находимся ли мы в контексте greenlet, созданном SQLAlchemy
|
|
14
|
+
if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
|
|
15
|
+
return await_only(self._loop.run_in_executor(None, partial(func, *args, **kwargs)))
|
|
16
|
+
else:
|
|
17
|
+
# Если нет, вызываем синхронно (например, внутри run_sync)
|
|
18
|
+
return func(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
def execute(self, operation, parameters=None):
|
|
21
|
+
if parameters is None:
|
|
22
|
+
return self._exec(self._sync_cursor.execute, operation)
|
|
23
|
+
else:
|
|
24
|
+
return self._exec(self._sync_cursor.execute, operation, parameters)
|
|
25
|
+
|
|
26
|
+
def executemany(self, operation, seq_of_parameters):
|
|
27
|
+
return self._exec(self._sync_cursor.executemany, operation, seq_of_parameters)
|
|
28
|
+
|
|
29
|
+
def fetchone(self):
|
|
30
|
+
return self._exec(self._sync_cursor.fetchone)
|
|
31
|
+
|
|
32
|
+
def fetchmany(self, size=None):
|
|
33
|
+
if size is None:
|
|
34
|
+
return self._exec(self._sync_cursor.fetchmany)
|
|
35
|
+
return self._exec(self._sync_cursor.fetchmany, size)
|
|
36
|
+
|
|
37
|
+
def fetchall(self):
|
|
38
|
+
return self._exec(self._sync_cursor.fetchall)
|
|
39
|
+
|
|
40
|
+
def close(self):
|
|
41
|
+
return self._exec(self._sync_cursor.close)
|
|
42
|
+
|
|
43
|
+
async def _async_soft_close(self):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
def nextset(self):
|
|
47
|
+
return self._exec(self._sync_cursor.nextset)
|
|
48
|
+
|
|
49
|
+
def __getattr__(self, name):
|
|
50
|
+
return getattr(self._sync_cursor, name)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AsyncConnection:
|
|
54
|
+
def __init__(self, sync_connection, loop):
|
|
55
|
+
self._sync_connection = sync_connection
|
|
56
|
+
self._loop = loop
|
|
57
|
+
|
|
58
|
+
def _exec(self, func, *args, **kwargs):
|
|
59
|
+
if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
|
|
60
|
+
return await_only(self._loop.run_in_executor(None, partial(func, *args, **kwargs)))
|
|
61
|
+
else:
|
|
62
|
+
return func(*args, **kwargs)
|
|
63
|
+
|
|
64
|
+
def cursor(self):
|
|
65
|
+
return AsyncCursor(self._sync_connection.cursor(), self._loop)
|
|
66
|
+
|
|
67
|
+
def commit(self):
|
|
68
|
+
return self._exec(self._sync_connection.commit)
|
|
69
|
+
|
|
70
|
+
def rollback(self):
|
|
71
|
+
return self._exec(self._sync_connection.rollback)
|
|
72
|
+
|
|
73
|
+
def close(self):
|
|
74
|
+
return self._exec(self._sync_connection.close)
|
|
75
|
+
|
|
76
|
+
def terminate(self):
|
|
77
|
+
return self._exec(self._sync_connection.close)
|
|
78
|
+
|
|
79
|
+
def __getattr__(self, name):
|
|
80
|
+
return getattr(self._sync_connection, name)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AsyncDBAPI:
|
|
84
|
+
def __init__(self, sync_dbapi):
|
|
85
|
+
self._sync_dbapi = sync_dbapi
|
|
86
|
+
self.paramstyle = getattr(sync_dbapi, "paramstyle", "qmark")
|
|
87
|
+
self.apilevel = getattr(sync_dbapi, "apilevel", "2.0")
|
|
88
|
+
self.threadsafety = getattr(sync_dbapi, "threadsafety", 0)
|
|
89
|
+
for attr in (
|
|
90
|
+
"Warning",
|
|
91
|
+
"Error",
|
|
92
|
+
"InterfaceError",
|
|
93
|
+
"DatabaseError",
|
|
94
|
+
"DataError",
|
|
95
|
+
"OperationalError",
|
|
96
|
+
"IntegrityError",
|
|
97
|
+
"InternalError",
|
|
98
|
+
"ProgrammingError",
|
|
99
|
+
"NotSupportedError",
|
|
100
|
+
):
|
|
101
|
+
if hasattr(sync_dbapi, attr):
|
|
102
|
+
setattr(self, attr, getattr(sync_dbapi, attr))
|
|
103
|
+
|
|
104
|
+
def connect(self, *args, **kwargs):
|
|
105
|
+
async_creator_fn = kwargs.pop("async_creator_fn", None)
|
|
106
|
+
loop = asyncio.get_running_loop()
|
|
107
|
+
|
|
108
|
+
def _connect():
|
|
109
|
+
if async_creator_fn is not None:
|
|
110
|
+
# Здесь мы не можем просто так вызвать await_only, если creator асинхронный
|
|
111
|
+
# Но fdb синхронный, так что всё ок.
|
|
112
|
+
# Если передан async_creator, то это для firebirdsql, но мы в fdb.py
|
|
113
|
+
return async_creator_fn(*args, **kwargs) # вернет корутину? нет, это коллбек
|
|
114
|
+
else:
|
|
115
|
+
return self._sync_dbapi.connect(*args, **kwargs)
|
|
116
|
+
|
|
117
|
+
if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
|
|
118
|
+
# Если async_creator_fn возвращает корутину, то await_only ее дождется
|
|
119
|
+
# Но для fdb это синхронный вызов, поэтому run_in_executor
|
|
120
|
+
sync_conn = await_only(loop.run_in_executor(None, partial(self._sync_dbapi.connect, *args, **kwargs)))
|
|
121
|
+
else:
|
|
122
|
+
sync_conn = self._sync_dbapi.connect(*args, **kwargs)
|
|
123
|
+
|
|
124
|
+
return AsyncConnection(sync_conn, loop)
|
|
125
|
+
|
|
126
|
+
from sqlalchemy.pool import AsyncAdaptedQueuePool
|
|
127
|
+
import sqlalchemy_firebird.fdb as fdb
|
|
128
|
+
from .compiler import PatchedFBTypeCompiler
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class AsyncFDBDialect(fdb.FBDialect_fdb):
|
|
132
|
+
name = "firebird.fdb_async"
|
|
133
|
+
driver = "fdb_async"
|
|
134
|
+
is_async = True
|
|
135
|
+
supports_statement_cache = False
|
|
136
|
+
poolclass = AsyncAdaptedQueuePool
|
|
137
|
+
# Explicitly set type compiler to ensure our patch is used
|
|
138
|
+
def __init__(self, *args, **kwargs):
|
|
139
|
+
super().__init__(*args, **kwargs)
|
|
140
|
+
self.type_compiler_instance = PatchedFBTypeCompiler(self)
|
|
141
|
+
self.type_compiler = self.type_compiler_instance
|
|
142
|
+
|
|
143
|
+
def is_disconnect(self, e, connection, cursor):
|
|
144
|
+
# Handle fdb disconnect errors which store error code in args[1]
|
|
145
|
+
# Base implementation checks for self.driver == "fdb"
|
|
146
|
+
if isinstance(e, self.dbapi.DatabaseError):
|
|
147
|
+
# We are essentially fdb
|
|
148
|
+
return (e.args[1] in (335546001, 335546003, 335546005)) or \
|
|
149
|
+
("Error writing data to the connection" in str(e))
|
|
150
|
+
return super().is_disconnect(e, connection, cursor)
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def import_dbapi(cls):
|
|
154
|
+
import fdb as sync_fdb
|
|
155
|
+
|
|
156
|
+
return AsyncDBAPI(sync_fdb)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def dbapi(cls):
|
|
160
|
+
return cls.import_dbapi()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import partial
|
|
3
|
+
from sqlalchemy.util.concurrency import await_only
|
|
4
|
+
from greenlet import getcurrent
|
|
5
|
+
from sqlalchemy.pool import AsyncAdaptedQueuePool
|
|
6
|
+
import sqlalchemy_firebird.firebird as firebird_sync
|
|
7
|
+
import firebird.driver as sync_driver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncCursor:
|
|
11
|
+
def __init__(self, sync_cursor, loop):
|
|
12
|
+
self._sync_cursor = sync_cursor
|
|
13
|
+
self._loop = loop
|
|
14
|
+
|
|
15
|
+
def _exec(self, func, *args, **kwargs):
|
|
16
|
+
if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
|
|
17
|
+
return await_only(self._loop.run_in_executor(None, partial(func, *args, **kwargs)))
|
|
18
|
+
else:
|
|
19
|
+
return func(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
def execute(self, operation, parameters=None):
|
|
22
|
+
if parameters is None:
|
|
23
|
+
return self._exec(self._sync_cursor.execute, operation)
|
|
24
|
+
else:
|
|
25
|
+
return self._exec(self._sync_cursor.execute, operation, parameters)
|
|
26
|
+
|
|
27
|
+
def executemany(self, operation, seq_of_parameters):
|
|
28
|
+
return self._exec(self._sync_cursor.executemany, operation, seq_of_parameters)
|
|
29
|
+
|
|
30
|
+
def fetchone(self):
|
|
31
|
+
return self._exec(self._sync_cursor.fetchone)
|
|
32
|
+
|
|
33
|
+
def fetchmany(self, size=None):
|
|
34
|
+
if size is None:
|
|
35
|
+
return self._exec(self._sync_cursor.fetchmany)
|
|
36
|
+
return self._exec(self._sync_cursor.fetchmany, size)
|
|
37
|
+
|
|
38
|
+
def fetchall(self):
|
|
39
|
+
return self._exec(self._sync_cursor.fetchall)
|
|
40
|
+
|
|
41
|
+
def close(self):
|
|
42
|
+
return self._exec(self._sync_cursor.close)
|
|
43
|
+
|
|
44
|
+
async def _async_soft_close(self):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def nextset(self):
|
|
48
|
+
return self._exec(self._sync_cursor.nextset)
|
|
49
|
+
|
|
50
|
+
def __getattr__(self, name):
|
|
51
|
+
return getattr(self._sync_cursor, name)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AsyncConnection:
|
|
55
|
+
def __init__(self, sync_connection, loop):
|
|
56
|
+
self._sync_connection = sync_connection
|
|
57
|
+
self._loop = loop
|
|
58
|
+
|
|
59
|
+
def _exec(self, func, *args, **kwargs):
|
|
60
|
+
if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
|
|
61
|
+
return await_only(self._loop.run_in_executor(None, partial(func, *args, **kwargs)))
|
|
62
|
+
else:
|
|
63
|
+
return func(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
def cursor(self):
|
|
66
|
+
return AsyncCursor(self._sync_connection.cursor(), self._loop)
|
|
67
|
+
|
|
68
|
+
def commit(self):
|
|
69
|
+
return self._exec(self._sync_connection.commit)
|
|
70
|
+
|
|
71
|
+
def rollback(self):
|
|
72
|
+
return self._exec(self._sync_connection.rollback)
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
return self._exec(self._sync_connection.close)
|
|
76
|
+
|
|
77
|
+
def terminate(self):
|
|
78
|
+
return self._exec(self._sync_connection.close)
|
|
79
|
+
|
|
80
|
+
def __getattr__(self, name):
|
|
81
|
+
return getattr(self._sync_connection, name)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AsyncDBAPI:
|
|
85
|
+
def __init__(self, sync_dbapi):
|
|
86
|
+
self._sync_dbapi = sync_dbapi
|
|
87
|
+
self.paramstyle = getattr(sync_dbapi, "paramstyle", "qmark")
|
|
88
|
+
self.apilevel = getattr(sync_dbapi, "apilevel", "2.0")
|
|
89
|
+
self.threadsafety = getattr(sync_dbapi, "threadsafety", 1)
|
|
90
|
+
for attr in (
|
|
91
|
+
"Warning",
|
|
92
|
+
"Error",
|
|
93
|
+
"InterfaceError",
|
|
94
|
+
"DatabaseError",
|
|
95
|
+
"DataError",
|
|
96
|
+
"OperationalError",
|
|
97
|
+
"IntegrityError",
|
|
98
|
+
"InternalError",
|
|
99
|
+
"ProgrammingError",
|
|
100
|
+
"NotSupportedError",
|
|
101
|
+
):
|
|
102
|
+
if hasattr(sync_dbapi, attr):
|
|
103
|
+
setattr(self, attr, getattr(sync_dbapi, attr))
|
|
104
|
+
|
|
105
|
+
def connect(self, *args, **kwargs):
|
|
106
|
+
async_creator_fn = kwargs.pop("async_creator_fn", None)
|
|
107
|
+
loop = asyncio.get_running_loop()
|
|
108
|
+
|
|
109
|
+
if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
|
|
110
|
+
sync_conn = await_only(loop.run_in_executor(None, partial(self._sync_dbapi.connect, *args, **kwargs)))
|
|
111
|
+
else:
|
|
112
|
+
sync_conn = self._sync_dbapi.connect(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
return AsyncConnection(sync_conn, loop)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
from .compiler import PatchedFBTypeCompiler
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AsyncFirebirdDialect(firebird_sync.FBDialect_firebird):
|
|
121
|
+
name = "firebird.firebird_async"
|
|
122
|
+
driver = "firebird_async"
|
|
123
|
+
is_async = True
|
|
124
|
+
supports_statement_cache = False
|
|
125
|
+
poolclass = AsyncAdaptedQueuePool
|
|
126
|
+
|
|
127
|
+
def __init__(self, *args, **kwargs):
|
|
128
|
+
super().__init__(*args, **kwargs)
|
|
129
|
+
self.type_compiler_instance = PatchedFBTypeCompiler(self)
|
|
130
|
+
self.type_compiler = self.type_compiler_instance
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def import_dbapi(cls):
|
|
134
|
+
return AsyncDBAPI(sync_driver)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def dbapi(cls):
|
|
138
|
+
return cls.import_dbapi()
|