firebird-driver 2.0.0__tar.gz → 2.0.2__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.
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/PKG-INFO +2 -2
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/README.md +1 -1
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/pyproject.toml +4 -1
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/__init__.py +1 -1
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/core.py +85 -20
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/.gitignore +0 -0
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/LICENSE +0 -0
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/config.py +0 -0
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/fbapi.py +0 -0
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/hooks.py +0 -0
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/interfaces.py +0 -0
- {firebird_driver-2.0.0 → firebird_driver-2.0.2}/src/firebird/driver/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firebird-driver
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Firebird driver for Python
|
|
5
5
|
Project-URL: Home, https://github.com/FirebirdSQL/python3-driver
|
|
6
6
|
Project-URL: Documentation, https://firebird-driver.rtfd.io
|
|
@@ -103,7 +103,7 @@ This project is using [pytest](https://docs.pytest.org/en/stable/) for testing,
|
|
|
103
103
|
tests add several options via `tests/conftest.py`.
|
|
104
104
|
|
|
105
105
|
By default, tests are configured to use local Firebird installation via network access.
|
|
106
|
-
To use local
|
|
106
|
+
To use local installation in `embedded` mode, comment out the section:
|
|
107
107
|
```
|
|
108
108
|
[tool.hatch.envs.hatch-test]
|
|
109
109
|
extra-args = ["--host=localhost"]
|
|
@@ -53,7 +53,7 @@ This project is using [pytest](https://docs.pytest.org/en/stable/) for testing,
|
|
|
53
53
|
tests add several options via `tests/conftest.py`.
|
|
54
54
|
|
|
55
55
|
By default, tests are configured to use local Firebird installation via network access.
|
|
56
|
-
To use local
|
|
56
|
+
To use local installation in `embedded` mode, comment out the section:
|
|
57
57
|
```
|
|
58
58
|
[tool.hatch.envs.hatch-test]
|
|
59
59
|
extra-args = ["--host=localhost"]
|
|
@@ -57,6 +57,9 @@ dependencies = [
|
|
|
57
57
|
|
|
58
58
|
[tool.hatch.envs.hatch-test]
|
|
59
59
|
extra-args = ["--host=localhost"]
|
|
60
|
+
extra-dependencies = [
|
|
61
|
+
"packaging>=25.0",
|
|
62
|
+
]
|
|
60
63
|
|
|
61
64
|
[[tool.hatch.envs.hatch-test.matrix]]
|
|
62
65
|
python = ["3.11", "3.12", "3.13"]
|
|
@@ -119,7 +122,7 @@ ban-relative-imports = "all"
|
|
|
119
122
|
|
|
120
123
|
[tool.ruff.lint.per-file-ignores]
|
|
121
124
|
# Tests can use magic values, assertions, and relative imports
|
|
122
|
-
"
|
|
125
|
+
"test_*" = ["PLR2004", "S101", "TID252"]
|
|
123
126
|
"fbapi.py" = ["N801", "E501"]
|
|
124
127
|
"interfaces.py" = ["ARG001", "ARG002", "N801", "N803", "E501", "FBT001"]
|
|
125
128
|
"hooks.py" = ["F401"]
|
|
@@ -53,6 +53,7 @@ import struct
|
|
|
53
53
|
import sys
|
|
54
54
|
import threading
|
|
55
55
|
import weakref
|
|
56
|
+
from urllib.parse import urlparse
|
|
56
57
|
from abc import ABC, abstractmethod
|
|
57
58
|
from collections.abc import Callable, Mapping, Sequence
|
|
58
59
|
from ctypes import addressof, byref, create_string_buffer, memmove, memset, pointer, string_at
|
|
@@ -1795,7 +1796,7 @@ class Connection:
|
|
|
1795
1796
|
self.__FIREBIRD_LIB__ = None
|
|
1796
1797
|
def __del__(self):
|
|
1797
1798
|
if not self.is_closed():
|
|
1798
|
-
warn(
|
|
1799
|
+
warn("Connection disposed without prior close()", ResourceWarning)
|
|
1799
1800
|
self._close()
|
|
1800
1801
|
self._close_internals()
|
|
1801
1802
|
self._att.detach()
|
|
@@ -2179,6 +2180,75 @@ def _connect_helper(dsn: str, host: str, port: str, database: str, protocol: Net
|
|
|
2179
2180
|
dsn += database
|
|
2180
2181
|
return dsn
|
|
2181
2182
|
|
|
2183
|
+
def _is_dsn(value: str) -> bool:
|
|
2184
|
+
"""
|
|
2185
|
+
Checks if the given string matches known patterns for Firebird DSNs.
|
|
2186
|
+
|
|
2187
|
+
This function analyzes the string for structures that are typical for
|
|
2188
|
+
Firebird connection strings, based on how the firebird-driver might
|
|
2189
|
+
construct them (per _connect_helper) or how the Firebird client
|
|
2190
|
+
library generally interprets them.
|
|
2191
|
+
|
|
2192
|
+
Args:
|
|
2193
|
+
value: The string to check.
|
|
2194
|
+
|
|
2195
|
+
Returns:
|
|
2196
|
+
True if the string matches a DSN pattern, False otherwise.
|
|
2197
|
+
"""
|
|
2198
|
+
if not isinstance(value, str) or not value.strip():
|
|
2199
|
+
# Empty or whitespace-only strings are not DSNs
|
|
2200
|
+
return False
|
|
2201
|
+
|
|
2202
|
+
# 1. Protocol-based DSNs (e.g., inet://localhost/employee)
|
|
2203
|
+
# These are directly produced by _connect_helper if a protocol is specified.
|
|
2204
|
+
try:
|
|
2205
|
+
parsed = urlparse(value)
|
|
2206
|
+
if parsed.scheme in [p.name.lower() for p in NetProtocol]:
|
|
2207
|
+
# A scheme-based DSN must have a network location (host/port) or a path (database part).
|
|
2208
|
+
# parsed.path.lstrip('/') handles cases like "xnet:///path/to/db" where netloc is empty.
|
|
2209
|
+
if parsed.netloc or (parsed.path and parsed.path.lstrip('/')):
|
|
2210
|
+
return True
|
|
2211
|
+
except ValueError:
|
|
2212
|
+
# urlparse can raise ValueError for some malformed inputs (e.g. "::1")
|
|
2213
|
+
# These are unlikely to be scheme-based DSNs in the firebird context.
|
|
2214
|
+
pass
|
|
2215
|
+
|
|
2216
|
+
# 2. Windows Named Pipes (e.g., \\server\pipe_name or \\server@port\pipe_name)
|
|
2217
|
+
# These are indicated by starting with \\.
|
|
2218
|
+
if value.startswith("\\\\"):
|
|
2219
|
+
# Basic check: must have some content after \\, and not be just \\ or \\\
|
|
2220
|
+
if len(value) > 2 and value[2] not in ['\\', '/']:
|
|
2221
|
+
return True
|
|
2222
|
+
|
|
2223
|
+
# 3. Classic host:database or host/port:database syntax
|
|
2224
|
+
# (e.g., localhost:employee, server/3050:/data/db.fdb)
|
|
2225
|
+
# This pattern should not be confused with "C:\path" or "http://..."
|
|
2226
|
+
colon_idx = value.find(':')
|
|
2227
|
+
if colon_idx > 0 and "://" not in value[:colon_idx]: # Colon exists, not at start, and not part of a scheme
|
|
2228
|
+
host_spec = value[:colon_idx]
|
|
2229
|
+
# db_spec = value[colon_idx+1:] # Not strictly needed for this check
|
|
2230
|
+
|
|
2231
|
+
# Avoid misinterpreting "C:\path" as "host C, db \path".
|
|
2232
|
+
# If host_spec is a single letter (like a drive) AND the char after colon is a path separator,
|
|
2233
|
+
# it's more likely an absolute path. os.path.isabs handles these better.
|
|
2234
|
+
is_windows_drive_abs_path_candidate = (
|
|
2235
|
+
len(host_spec) == 1 and host_spec[0].isalpha() and
|
|
2236
|
+
len(value) > colon_idx + 1 and value[colon_idx+1] in ('/', '\\')
|
|
2237
|
+
)
|
|
2238
|
+
|
|
2239
|
+
if not is_windows_drive_abs_path_candidate:
|
|
2240
|
+
# host_spec should not contain backslashes if it's a hostname.
|
|
2241
|
+
# It can contain a forward slash for host/port.
|
|
2242
|
+
if '\\' not in host_spec:
|
|
2243
|
+
if '/' in host_spec: # Potential host/port:db
|
|
2244
|
+
# e.g., "server/3050:dbname"
|
|
2245
|
+
parts = host_spec.split('/', 1) # Split only on the first /
|
|
2246
|
+
if len(parts) == 2 and parts[0] and parts[1].isdigit(): # host part and port part (digits)
|
|
2247
|
+
return True
|
|
2248
|
+
elif host_spec: # Plain host:db, ensure host_spec is not empty
|
|
2249
|
+
# e.g., "localhost:dbname", "localhost:/path/to/db", "localhost:C:relative_path_on_C"
|
|
2250
|
+
return True
|
|
2251
|
+
return False
|
|
2182
2252
|
def __make_connection(dsn: str, utf8filename: bool, dpb: bytes, sql_dialect: int, charset: str, # noqa: FBT001
|
|
2183
2253
|
crypt_callback: iCryptKeyCallbackImpl, *, create: bool) -> Connection:
|
|
2184
2254
|
with a.get_api().master.get_dispatcher() as provider:
|
|
@@ -2244,13 +2314,14 @@ def connect(database: str | Path, *, user: str | None=None, password: str | None
|
|
|
2244
2314
|
if isinstance(database, Path):
|
|
2245
2315
|
database = str(database)
|
|
2246
2316
|
db_config: DatabaseConfig = driver_config.get_database(database)
|
|
2317
|
+
dsn: str | None = None
|
|
2247
2318
|
if db_config is None:
|
|
2248
2319
|
db_config = driver_config.db_defaults
|
|
2249
|
-
# we'll assume that 'database' is 'dsn'
|
|
2250
|
-
dsn = database
|
|
2251
|
-
database = None
|
|
2252
2320
|
srv_config = driver_config.server_defaults
|
|
2253
|
-
|
|
2321
|
+
if _is_dsn(database):
|
|
2322
|
+
dsn = database
|
|
2323
|
+
database = None
|
|
2324
|
+
srv_config.host.clear()
|
|
2254
2325
|
else:
|
|
2255
2326
|
database = db_config.database.value
|
|
2256
2327
|
dsn = db_config.dsn.value
|
|
@@ -2328,11 +2399,11 @@ def create_database(database: str | Path, *, user: str | None=None, password: st
|
|
|
2328
2399
|
db_config: DatabaseConfig = driver_config.get_database(database)
|
|
2329
2400
|
if db_config is None:
|
|
2330
2401
|
db_config = driver_config.db_defaults
|
|
2331
|
-
# we'll assume that 'database' is 'dsn'
|
|
2332
|
-
dsn = database
|
|
2333
|
-
database = None
|
|
2334
2402
|
srv_config = driver_config.server_defaults
|
|
2335
|
-
|
|
2403
|
+
if _is_dsn(database):
|
|
2404
|
+
dsn = database
|
|
2405
|
+
database = None
|
|
2406
|
+
srv_config.host.clear()
|
|
2336
2407
|
else:
|
|
2337
2408
|
database = db_config.database.value
|
|
2338
2409
|
dsn = db_config.dsn.value
|
|
@@ -2537,7 +2608,7 @@ class TransactionManager:
|
|
|
2537
2608
|
self.close()
|
|
2538
2609
|
def __del__(self):
|
|
2539
2610
|
if self._tra is not None:
|
|
2540
|
-
warn(
|
|
2611
|
+
warn("Transaction disposed while active", ResourceWarning)
|
|
2541
2612
|
self._finish()
|
|
2542
2613
|
def __dead_con(self, obj: Connection) -> None: # noqa: ARG002
|
|
2543
2614
|
self._connection = None
|
|
@@ -2947,12 +3018,8 @@ class Statement:
|
|
|
2947
3018
|
self.free()
|
|
2948
3019
|
def __del__(self):
|
|
2949
3020
|
if self._in_meta or self._out_meta or self._istmt:
|
|
2950
|
-
warn(
|
|
3021
|
+
warn("Statement disposed without prior free()", ResourceWarning)
|
|
2951
3022
|
self.free()
|
|
2952
|
-
def __str__(self):
|
|
2953
|
-
return f'{self.logging_id}[{self.sql}]'
|
|
2954
|
-
def __repr__(self):
|
|
2955
|
-
return str(self)
|
|
2956
3023
|
def __dead_con(self, obj: Connection) -> None: # noqa: ARG002
|
|
2957
3024
|
self._connection = None
|
|
2958
3025
|
def __get_plan(self, *, detailed: bool) -> str:
|
|
@@ -3085,10 +3152,8 @@ class BlobReader(io.IOBase):
|
|
|
3085
3152
|
self.close()
|
|
3086
3153
|
def __del__(self):
|
|
3087
3154
|
if self._blob is not None:
|
|
3088
|
-
warn(
|
|
3155
|
+
warn("BlobReader disposed without prior close()", ResourceWarning)
|
|
3089
3156
|
self.close()
|
|
3090
|
-
def __repr__(self):
|
|
3091
|
-
return f'{self.logging_id}[size={self.length}]'
|
|
3092
3157
|
def flush(self) -> None:
|
|
3093
3158
|
"""Does nothing.
|
|
3094
3159
|
"""
|
|
@@ -3292,7 +3357,7 @@ class Cursor:
|
|
|
3292
3357
|
self.close()
|
|
3293
3358
|
def __del__(self):
|
|
3294
3359
|
if self._result is not None or self._stmt is not None or self.__blob_readers:
|
|
3295
|
-
warn(
|
|
3360
|
+
warn("Cursor disposed without prior close()", ResourceWarning)
|
|
3296
3361
|
self.close()
|
|
3297
3362
|
def __next__(self):
|
|
3298
3363
|
if (row := self.fetchone()) is not None:
|
|
@@ -5547,7 +5612,7 @@ class Server:
|
|
|
5547
5612
|
self.close()
|
|
5548
5613
|
def __del__(self):
|
|
5549
5614
|
if self._svc is not None:
|
|
5550
|
-
warn(
|
|
5615
|
+
warn("Server disposed without prior close()", ResourceWarning)
|
|
5551
5616
|
self.close()
|
|
5552
5617
|
def __next__(self):
|
|
5553
5618
|
if (line := self.readline()) is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|