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.
Files changed (31) hide show
  1. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/.gitignore +1 -0
  2. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/PKG-INFO +26 -22
  3. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/README.md +22 -21
  4. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/pyproject.toml +5 -3
  5. sqlalchemy_firebird_async-0.2.1/src/sqlalchemy_firebird_async/compiler.py +12 -0
  6. sqlalchemy_firebird_async-0.2.1/src/sqlalchemy_firebird_async/fdb.py +160 -0
  7. sqlalchemy_firebird_async-0.2.1/src/sqlalchemy_firebird_async/firebird_driver.py +138 -0
  8. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/firebirdsql.py +53 -47
  9. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/tests/conftest.py +8 -7
  10. sqlalchemy_firebird_async-0.2.1/tests/test_basic.py +80 -0
  11. sqlalchemy_firebird_async-0.2.1/tests/test_load.py +131 -0
  12. sqlalchemy_firebird_async-0.2.1/tests/test_terminate.py +59 -0
  13. sqlalchemy_firebird_async-0.2.1/tests/test_types.py +141 -0
  14. sqlalchemy_firebird_async-0.1.0/check_query.py +0 -22
  15. sqlalchemy_firebird_async-0.1.0/check_simple.py +0 -12
  16. sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/fdb.py +0 -115
  17. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/PKG-INFO +0 -50
  18. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/README.md +0 -26
  19. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/pyproject.toml +0 -39
  20. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/__init__.py +0 -0
  21. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/fdb.py +0 -115
  22. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/firebirdsql.py +0 -154
  23. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/uv.lock +0 -204
  24. sqlalchemy_firebird_async-0.1.0/temp_check/test_install/check.py +0 -29
  25. sqlalchemy_firebird_async-0.1.0/temp_check/test_install/db/sql_models.py +0 -140
  26. sqlalchemy_firebird_async-0.1.0/temp_check/test_install/main.py +0 -175
  27. sqlalchemy_firebird_async-0.1.0/tests/test_basic.py +0 -39
  28. sqlalchemy_firebird_async-0.1.0/tests/test_load.py +0 -79
  29. sqlalchemy_firebird_async-0.1.0/uv.lock +0 -468
  30. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/LICENSE +0 -0
  31. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/__init__.py +0 -0
@@ -147,3 +147,4 @@ cython_debug/
147
147
 
148
148
  # PyCharm
149
149
  .idea/
150
+ uv.lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-firebird-async
3
- Version: 0.1.0
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 two underlying drivers:
41
- 1. **`fdb`** (Recommended) - Runs the official C-based driver in a thread pool. Fast and stable.
42
- 2. **`firebirdsql`** - Pure Python asyncio driver. Currently experimental due to upstream issues.
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
- # Recommended: Install with the FDB driver (Threaded, Fast)
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 (Recommended)
67
+ ### 1. Using FDB Driver (Legacy)
61
68
 
62
- This dialect runs the official `fdb` driver in a thread pool (`run_in_executor`). While not "truly" async at the socket level, it provides the best performance and stability currently available for Firebird in Python.
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
- async with engine.begin() as conn:
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
- # Using AsyncSession
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
- await engine.dispose()
90
+ **URL Scheme:** `firebird+firebird_async://`
90
91
 
91
- if __name__ == "__main__":
92
- asyncio.run(main())
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
- ### 2. Using Native Async Driver (firebirdsql)
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** (Recommended) | TCP/IP | `firebird+fdb_async://user:pass@host:port/db_path` |
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 two underlying drivers:
12
- 1. **`fdb`** (Recommended) - Runs the official C-based driver in a thread pool. Fast and stable.
13
- 2. **`firebirdsql`** - Pure Python asyncio driver. Currently experimental due to upstream issues.
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
- # Recommended: Install with the FDB driver (Threaded, Fast)
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 (Recommended)
35
+ ### 1. Using FDB Driver (Legacy)
32
36
 
33
- This dialect runs the official `fdb` driver in a thread pool (`run_in_executor`). While not "truly" async at the socket level, it provides the best performance and stability currently available for Firebird in Python.
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
- async with engine.begin() as conn:
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
- # Using AsyncSession
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
- await engine.dispose()
58
+ **URL Scheme:** `firebird+firebird_async://`
61
59
 
62
- if __name__ == "__main__":
63
- asyncio.run(main())
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
- ### 2. Using Native Async Driver (firebirdsql)
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** (Recommended) | TCP/IP | `firebird+fdb_async://user:pass@host:port/db_path` |
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.0"
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
- all = ["fdb>=2.0", "firebirdsql>=1.0"]
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()