sqlalchemy-firebird-async 0.2.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.2.0 → sqlalchemy_firebird_async-0.2.1}/PKG-INFO +1 -1
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/pyproject.toml +1 -1
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/fdb.py +3 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/firebird_driver.py +3 -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.2.0 → sqlalchemy_firebird_async-0.2.1}/.gitignore +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/LICENSE +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/README.md +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/__init__.py +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/compiler.py +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/src/sqlalchemy_firebird_async/firebirdsql.py +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/tests/conftest.py +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/tests/test_basic.py +0 -0
- {sqlalchemy_firebird_async-0.2.0 → sqlalchemy_firebird_async-0.2.1}/tests/test_load.py +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from sqlalchemy import text
|
|
3
|
+
|
|
4
|
+
@pytest.mark.asyncio
|
|
5
|
+
async def test_engine_terminate(async_engine):
|
|
6
|
+
"""
|
|
7
|
+
Тест проверяет корректность закрытия соединений при dispose.
|
|
8
|
+
Это должно вызывать do_terminate -> terminate.
|
|
9
|
+
"""
|
|
10
|
+
# 1. Открываем соединение
|
|
11
|
+
async with async_engine.connect() as conn:
|
|
12
|
+
await conn.execute(text("SELECT 1 FROM rdb$database"))
|
|
13
|
+
# Не закрываем явно (хотя контекст менеджер закроет)
|
|
14
|
+
|
|
15
|
+
# 2. Форсируем закрытие пула
|
|
16
|
+
# dispose() вызывает pool.dispose(), который закрывает все соединения.
|
|
17
|
+
# Если пул был QueuePool (стандартный), он может попытаться вызвать terminate
|
|
18
|
+
# для проверенных соединений, если они считаются "invalid" или при очистке.
|
|
19
|
+
|
|
20
|
+
# Чтобы гарантированно вызвать terminate, можно попробовать сымитировать ошибку
|
|
21
|
+
# или просто положиться на то, что пул вызывает terminate при сбросе.
|
|
22
|
+
|
|
23
|
+
await async_engine.dispose()
|
|
24
|
+
|
|
25
|
+
# 3. Прямой тест метода terminate на объекте соединения (низкоуровневый)
|
|
26
|
+
# Нам нужно добраться до raw connection
|
|
27
|
+
raw_conn = await async_engine.raw_connection()
|
|
28
|
+
try:
|
|
29
|
+
# В SQLAlchemy 2.0+ raw_connection возвращает адаптер
|
|
30
|
+
# Нам нужно вызвать метод terminate, который ожидает диалект
|
|
31
|
+
if hasattr(raw_conn, "terminate"):
|
|
32
|
+
raw_conn.terminate()
|
|
33
|
+
else:
|
|
34
|
+
# Если метода нет, это уже ошибка (AttributeError бы вылетел выше, если бы вызов шел)
|
|
35
|
+
# Но ошибка из стектрейса пользователя была в do_terminate диалекта
|
|
36
|
+
|
|
37
|
+
# Эмулируем то, что делает пул:
|
|
38
|
+
# dialect.do_terminate(dbapi_connection)
|
|
39
|
+
dialect = async_engine.dialect
|
|
40
|
+
# raw_conn это AsyncAdapt_dbapi_connection
|
|
41
|
+
# Внутри него .dbapi_connection это наш AsyncConnection
|
|
42
|
+
|
|
43
|
+
# Но terminate вызывается у dbapi_connection.
|
|
44
|
+
# Попробуем вызвать его у нашего AsyncConnection
|
|
45
|
+
real_conn = raw_conn.driver_connection
|
|
46
|
+
|
|
47
|
+
# Проверяем наличие метода, так как его отсутствие вызывает ошибку
|
|
48
|
+
# Если мы его не добавим, тут будет AttributeError (если вызывать)
|
|
49
|
+
|
|
50
|
+
# Эмуляция вызова из диалекта:
|
|
51
|
+
try:
|
|
52
|
+
dialect.do_terminate(real_conn)
|
|
53
|
+
except AttributeError as e:
|
|
54
|
+
if "'terminate'" in str(e):
|
|
55
|
+
pytest.fail(f"Missing terminate method: {e}")
|
|
56
|
+
else:
|
|
57
|
+
raise e
|
|
58
|
+
finally:
|
|
59
|
+
raw_conn.close()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import decimal
|
|
3
|
+
import datetime
|
|
4
|
+
from sqlalchemy import text
|
|
5
|
+
|
|
6
|
+
@pytest.mark.asyncio
|
|
7
|
+
async def test_primitive_types(async_engine):
|
|
8
|
+
"""Тест базовых типов: INT, BIGINT, FLOAT, BOOLEAN, VARCHAR, DECIMAL."""
|
|
9
|
+
# 1. DDL
|
|
10
|
+
async with async_engine.begin() as conn:
|
|
11
|
+
try: await conn.execute(text("DROP TABLE all_types"))
|
|
12
|
+
except Exception: pass
|
|
13
|
+
|
|
14
|
+
await conn.execute(text("""
|
|
15
|
+
CREATE TABLE all_types (
|
|
16
|
+
id INTEGER PRIMARY KEY,
|
|
17
|
+
f_integer INTEGER,
|
|
18
|
+
f_bigint BIGINT,
|
|
19
|
+
f_float FLOAT,
|
|
20
|
+
f_boolean BOOLEAN,
|
|
21
|
+
f_string VARCHAR(100),
|
|
22
|
+
f_decimal DECIMAL(10, 2)
|
|
23
|
+
)
|
|
24
|
+
"""))
|
|
25
|
+
|
|
26
|
+
# 2. DML
|
|
27
|
+
test_data = {
|
|
28
|
+
"id": 1,
|
|
29
|
+
"f_integer": 42,
|
|
30
|
+
"f_bigint": 9223372036854775807,
|
|
31
|
+
"f_float": 3.14159,
|
|
32
|
+
"f_boolean": True,
|
|
33
|
+
"f_string": "Hello Firebird",
|
|
34
|
+
"f_decimal": decimal.Decimal("123.45")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async with async_engine.begin() as conn:
|
|
38
|
+
await conn.execute(
|
|
39
|
+
text("""
|
|
40
|
+
INSERT INTO all_types (id, f_integer, f_bigint, f_float, f_boolean, f_string, f_decimal)
|
|
41
|
+
VALUES (:id, :f_integer, :f_bigint, :f_float, :f_boolean, :f_string, :f_decimal)
|
|
42
|
+
"""),
|
|
43
|
+
test_data
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# 3. SELECT
|
|
47
|
+
async with async_engine.connect() as conn:
|
|
48
|
+
result = await conn.execute(text("SELECT * FROM all_types WHERE id = 1"))
|
|
49
|
+
row = result.fetchone()
|
|
50
|
+
assert row.f_integer == 42
|
|
51
|
+
assert row.f_bigint == 9223372036854775807
|
|
52
|
+
assert abs(row.f_float - 3.14159) < 0.00001
|
|
53
|
+
assert row.f_boolean is True
|
|
54
|
+
assert row.f_string == "Hello Firebird"
|
|
55
|
+
assert row.f_decimal == decimal.Decimal("123.45")
|
|
56
|
+
|
|
57
|
+
@pytest.mark.asyncio
|
|
58
|
+
async def test_complex_types_fb4(async_engine):
|
|
59
|
+
"""
|
|
60
|
+
Тестирование сложных типов FB4 (INT128, Time Zones).
|
|
61
|
+
Пропускается для fdb_async, так как старый драйвер их не поддерживает.
|
|
62
|
+
"""
|
|
63
|
+
if "fdb_async" in async_engine.url.drivername:
|
|
64
|
+
pytest.skip("fdb driver does not support INT128 or Time Zones")
|
|
65
|
+
|
|
66
|
+
now_tz = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
|
|
67
|
+
test_val_38 = decimal.Decimal("1234567890123456789012345678.9012345678")
|
|
68
|
+
test_blob = "Very large text blob " * 100
|
|
69
|
+
|
|
70
|
+
async with async_engine.begin() as conn:
|
|
71
|
+
try: await conn.execute(text("DROP TABLE complex_types"))
|
|
72
|
+
except Exception: pass
|
|
73
|
+
await conn.execute(text("""
|
|
74
|
+
CREATE TABLE complex_types (
|
|
75
|
+
id INTEGER PRIMARY KEY,
|
|
76
|
+
f_numeric_38 NUMERIC(38, 10),
|
|
77
|
+
f_timestamp_tz TIMESTAMP WITH TIME ZONE,
|
|
78
|
+
f_blob BLOB SUB_TYPE TEXT
|
|
79
|
+
)
|
|
80
|
+
"""))
|
|
81
|
+
|
|
82
|
+
async with async_engine.begin() as conn:
|
|
83
|
+
await conn.execute(
|
|
84
|
+
text("""
|
|
85
|
+
INSERT INTO complex_types (id, f_numeric_38, f_timestamp_tz, f_blob)
|
|
86
|
+
VALUES (:id, :f_numeric_38, :f_timestamp_tz, :f_blob)
|
|
87
|
+
"""),
|
|
88
|
+
{"id": 1, "f_numeric_38": test_val_38, "f_timestamp_tz": now_tz, "f_blob": test_blob}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async with async_engine.connect() as conn:
|
|
92
|
+
result = await conn.execute(text("SELECT * FROM complex_types WHERE id = 1"))
|
|
93
|
+
row = result.fetchone()
|
|
94
|
+
assert row.f_numeric_38 == test_val_38
|
|
95
|
+
assert isinstance(row.f_timestamp_tz, datetime.datetime)
|
|
96
|
+
assert abs(row.f_timestamp_tz.timestamp() - now_tz.timestamp()) < 1.0
|
|
97
|
+
assert row.f_blob == test_blob
|
|
98
|
+
|
|
99
|
+
@pytest.mark.asyncio
|
|
100
|
+
async def test_aggregations(async_engine):
|
|
101
|
+
"""Проверка агрегатных функций."""
|
|
102
|
+
if "fdb_async" in async_engine.url.drivername:
|
|
103
|
+
pytest.skip("fdb driver has issues with SQLDA for aggregations in async wrapper")
|
|
104
|
+
|
|
105
|
+
async with async_engine.begin() as conn:
|
|
106
|
+
try: await conn.execute(text("DROP TABLE test_agg"))
|
|
107
|
+
except Exception: pass
|
|
108
|
+
await conn.execute(text("CREATE TABLE test_agg (val DECIMAL(10,2))"))
|
|
109
|
+
|
|
110
|
+
async with async_engine.begin() as conn:
|
|
111
|
+
for v in [10.5, 20.5, 30.0]:
|
|
112
|
+
await conn.execute(text("INSERT INTO test_agg (val) VALUES (:v)"), {"v": v})
|
|
113
|
+
|
|
114
|
+
async with async_engine.connect() as conn:
|
|
115
|
+
res = await conn.execute(text("SELECT SUM(val), COUNT(*) FROM test_agg"))
|
|
116
|
+
s, c = res.fetchone()
|
|
117
|
+
assert s == decimal.Decimal("61.00")
|
|
118
|
+
assert c == 3
|
|
119
|
+
|
|
120
|
+
@pytest.mark.asyncio
|
|
121
|
+
async def test_insert_returning(async_engine):
|
|
122
|
+
"""Проверка RETURNING."""
|
|
123
|
+
async with async_engine.begin() as conn:
|
|
124
|
+
try: await conn.execute(text("DROP TABLE test_ret"))
|
|
125
|
+
except Exception: pass
|
|
126
|
+
await conn.execute(text("CREATE TABLE test_ret (id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(50))"))
|
|
127
|
+
|
|
128
|
+
async with async_engine.begin() as conn:
|
|
129
|
+
res = await conn.execute(
|
|
130
|
+
text("INSERT INTO test_ret (name) VALUES (:name) RETURNING id"),
|
|
131
|
+
{"name": "test"}
|
|
132
|
+
)
|
|
133
|
+
new_id = res.scalar()
|
|
134
|
+
assert isinstance(new_id, int)
|
|
135
|
+
|
|
136
|
+
@pytest.mark.asyncio
|
|
137
|
+
async def test_statement_string_compilation(async_engine):
|
|
138
|
+
"""Проверка компиляции запроса в строку (для print)."""
|
|
139
|
+
stmt = text("SELECT 1 FROM rdb$database WHERE 1 = :id").bindparams(id=1)
|
|
140
|
+
compiled_str = str(stmt.compile(async_engine.sync_engine))
|
|
141
|
+
assert "1 = " in compiled_str
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|