sqlalchemy-firebird-async 0.1.0__tar.gz → 0.2.0__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 (29) hide show
  1. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/.gitignore +1 -0
  2. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/PKG-INFO +26 -22
  3. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/README.md +22 -21
  4. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/pyproject.toml +5 -3
  5. sqlalchemy_firebird_async-0.2.0/src/sqlalchemy_firebird_async/compiler.py +12 -0
  6. sqlalchemy_firebird_async-0.2.0/src/sqlalchemy_firebird_async/fdb.py +157 -0
  7. sqlalchemy_firebird_async-0.2.0/src/sqlalchemy_firebird_async/firebird_driver.py +135 -0
  8. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/src/sqlalchemy_firebird_async/firebirdsql.py +53 -47
  9. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/tests/conftest.py +8 -7
  10. sqlalchemy_firebird_async-0.2.0/tests/test_basic.py +80 -0
  11. sqlalchemy_firebird_async-0.2.0/tests/test_load.py +131 -0
  12. sqlalchemy_firebird_async-0.1.0/check_query.py +0 -22
  13. sqlalchemy_firebird_async-0.1.0/check_simple.py +0 -12
  14. sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/fdb.py +0 -115
  15. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/PKG-INFO +0 -50
  16. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/README.md +0 -26
  17. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/pyproject.toml +0 -39
  18. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/__init__.py +0 -0
  19. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/fdb.py +0 -115
  20. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/src/sqlalchemy_firebird_async/firebirdsql.py +0 -154
  21. sqlalchemy_firebird_async-0.1.0/temp_check/sqlalchemy_firebird_async-0.1.0/uv.lock +0 -204
  22. sqlalchemy_firebird_async-0.1.0/temp_check/test_install/check.py +0 -29
  23. sqlalchemy_firebird_async-0.1.0/temp_check/test_install/db/sql_models.py +0 -140
  24. sqlalchemy_firebird_async-0.1.0/temp_check/test_install/main.py +0 -175
  25. sqlalchemy_firebird_async-0.1.0/tests/test_basic.py +0 -39
  26. sqlalchemy_firebird_async-0.1.0/tests/test_load.py +0 -79
  27. sqlalchemy_firebird_async-0.1.0/uv.lock +0 -468
  28. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/LICENSE +0 -0
  29. {sqlalchemy_firebird_async-0.1.0 → sqlalchemy_firebird_async-0.2.0}/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.0
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.0"
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,157 @@
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 __getattr__(self, name):
77
+ return getattr(self._sync_connection, name)
78
+
79
+
80
+ class AsyncDBAPI:
81
+ def __init__(self, sync_dbapi):
82
+ self._sync_dbapi = sync_dbapi
83
+ self.paramstyle = getattr(sync_dbapi, "paramstyle", "qmark")
84
+ self.apilevel = getattr(sync_dbapi, "apilevel", "2.0")
85
+ self.threadsafety = getattr(sync_dbapi, "threadsafety", 0)
86
+ for attr in (
87
+ "Warning",
88
+ "Error",
89
+ "InterfaceError",
90
+ "DatabaseError",
91
+ "DataError",
92
+ "OperationalError",
93
+ "IntegrityError",
94
+ "InternalError",
95
+ "ProgrammingError",
96
+ "NotSupportedError",
97
+ ):
98
+ if hasattr(sync_dbapi, attr):
99
+ setattr(self, attr, getattr(sync_dbapi, attr))
100
+
101
+ def connect(self, *args, **kwargs):
102
+ async_creator_fn = kwargs.pop("async_creator_fn", None)
103
+ loop = asyncio.get_running_loop()
104
+
105
+ def _connect():
106
+ if async_creator_fn is not None:
107
+ # Здесь мы не можем просто так вызвать await_only, если creator асинхронный
108
+ # Но fdb синхронный, так что всё ок.
109
+ # Если передан async_creator, то это для firebirdsql, но мы в fdb.py
110
+ return async_creator_fn(*args, **kwargs) # вернет корутину? нет, это коллбек
111
+ else:
112
+ return self._sync_dbapi.connect(*args, **kwargs)
113
+
114
+ if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
115
+ # Если async_creator_fn возвращает корутину, то await_only ее дождется
116
+ # Но для fdb это синхронный вызов, поэтому run_in_executor
117
+ sync_conn = await_only(loop.run_in_executor(None, partial(self._sync_dbapi.connect, *args, **kwargs)))
118
+ else:
119
+ sync_conn = self._sync_dbapi.connect(*args, **kwargs)
120
+
121
+ return AsyncConnection(sync_conn, loop)
122
+
123
+ from sqlalchemy.pool import AsyncAdaptedQueuePool
124
+ import sqlalchemy_firebird.fdb as fdb
125
+ from .compiler import PatchedFBTypeCompiler
126
+
127
+
128
+ class AsyncFDBDialect(fdb.FBDialect_fdb):
129
+ name = "firebird.fdb_async"
130
+ driver = "fdb_async"
131
+ is_async = True
132
+ supports_statement_cache = False
133
+ poolclass = AsyncAdaptedQueuePool
134
+ # Explicitly set type compiler to ensure our patch is used
135
+ def __init__(self, *args, **kwargs):
136
+ super().__init__(*args, **kwargs)
137
+ self.type_compiler_instance = PatchedFBTypeCompiler(self)
138
+ self.type_compiler = self.type_compiler_instance
139
+
140
+ def is_disconnect(self, e, connection, cursor):
141
+ # Handle fdb disconnect errors which store error code in args[1]
142
+ # Base implementation checks for self.driver == "fdb"
143
+ if isinstance(e, self.dbapi.DatabaseError):
144
+ # We are essentially fdb
145
+ return (e.args[1] in (335546001, 335546003, 335546005)) or \
146
+ ("Error writing data to the connection" in str(e))
147
+ return super().is_disconnect(e, connection, cursor)
148
+
149
+ @classmethod
150
+ def import_dbapi(cls):
151
+ import fdb as sync_fdb
152
+
153
+ return AsyncDBAPI(sync_fdb)
154
+
155
+ @classmethod
156
+ def dbapi(cls):
157
+ return cls.import_dbapi()
@@ -0,0 +1,135 @@
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 __getattr__(self, name):
78
+ return getattr(self._sync_connection, name)
79
+
80
+
81
+ class AsyncDBAPI:
82
+ def __init__(self, sync_dbapi):
83
+ self._sync_dbapi = sync_dbapi
84
+ self.paramstyle = getattr(sync_dbapi, "paramstyle", "qmark")
85
+ self.apilevel = getattr(sync_dbapi, "apilevel", "2.0")
86
+ self.threadsafety = getattr(sync_dbapi, "threadsafety", 1)
87
+ for attr in (
88
+ "Warning",
89
+ "Error",
90
+ "InterfaceError",
91
+ "DatabaseError",
92
+ "DataError",
93
+ "OperationalError",
94
+ "IntegrityError",
95
+ "InternalError",
96
+ "ProgrammingError",
97
+ "NotSupportedError",
98
+ ):
99
+ if hasattr(sync_dbapi, attr):
100
+ setattr(self, attr, getattr(sync_dbapi, attr))
101
+
102
+ def connect(self, *args, **kwargs):
103
+ async_creator_fn = kwargs.pop("async_creator_fn", None)
104
+ loop = asyncio.get_running_loop()
105
+
106
+ if getattr(getcurrent(), "__sqlalchemy_greenlet_provider__", None):
107
+ sync_conn = await_only(loop.run_in_executor(None, partial(self._sync_dbapi.connect, *args, **kwargs)))
108
+ else:
109
+ sync_conn = self._sync_dbapi.connect(*args, **kwargs)
110
+
111
+ return AsyncConnection(sync_conn, loop)
112
+
113
+
114
+ from .compiler import PatchedFBTypeCompiler
115
+
116
+
117
+ class AsyncFirebirdDialect(firebird_sync.FBDialect_firebird):
118
+ name = "firebird.firebird_async"
119
+ driver = "firebird_async"
120
+ is_async = True
121
+ supports_statement_cache = False
122
+ poolclass = AsyncAdaptedQueuePool
123
+
124
+ def __init__(self, *args, **kwargs):
125
+ super().__init__(*args, **kwargs)
126
+ self.type_compiler_instance = PatchedFBTypeCompiler(self)
127
+ self.type_compiler = self.type_compiler_instance
128
+
129
+ @classmethod
130
+ def import_dbapi(cls):
131
+ return AsyncDBAPI(sync_driver)
132
+
133
+ @classmethod
134
+ def dbapi(cls):
135
+ return cls.import_dbapi()