sqlalchemy-seerdb 0.2.0__tar.gz → 0.2.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.
Files changed (19) hide show
  1. {sqlalchemy_seerdb-0.2.0/sqlalchemy_seerdb.egg-info → sqlalchemy_seerdb-0.2.2}/PKG-INFO +21 -4
  2. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/README.md +20 -3
  3. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/pyproject.toml +1 -1
  4. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb/__init__.py +1 -1
  5. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb/seerdb.py +55 -3
  6. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2/sqlalchemy_seerdb.egg-info}/PKG-INFO +21 -4
  7. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb.egg-info/SOURCES.txt +1 -0
  8. sqlalchemy_seerdb-0.2.2/test/test_boolean.py +32 -0
  9. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/LICENSES/MIT.txt +0 -0
  10. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/setup.cfg +0 -0
  11. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb/provision.py +0 -0
  12. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb/requirements.py +0 -0
  13. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb.egg-info/dependency_links.txt +0 -0
  14. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb.egg-info/entry_points.txt +0 -0
  15. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb.egg-info/requires.txt +0 -0
  16. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/sqlalchemy_seerdb.egg-info/top_level.txt +0 -0
  17. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/test/test_connect_args.py +0 -0
  18. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/test/test_suite.py +0 -0
  19. {sqlalchemy_seerdb-0.2.0 → sqlalchemy_seerdb-0.2.2}/test/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-seerdb
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: SQLAlchemy dialect for the seerdb driver
5
5
  Author-email: Peter Lemenkov <lemenkov@gmail.com>
6
6
  License-Expression: MIT
@@ -93,16 +93,33 @@ builds tables there.
93
93
 
94
94
  The test account cannot create it (`ORA-01031`), so run this as a DBA once:
95
95
 
96
+ ```
97
+ python test/prepare_test_schema.py --host localhost --service XE \
98
+ --dba-password <SYSTEM password> --test-user <your test user>
99
+ ```
100
+
101
+ It is safe to rerun: each statement is attempted on its own and one that has
102
+ already been applied is reported and skipped. What it runs, for a DBA who would
103
+ rather type it:
104
+
96
105
  ```sql
106
+ -- the test account's own tables must live in USERS: the Oracle dialect hides
107
+ -- SYSTEM-tablespace tables from reflection, and the suite then never empties
108
+ -- them between tests (rows "survive", ORA-00001)
109
+ ALTER USER <your test user> DEFAULT TABLESPACE USERS;
110
+ ALTER USER <your test user> QUOTA UNLIMITED ON USERS;
111
+
97
112
  CREATE USER test_schema IDENTIFIED BY test_schema;
98
113
  GRANT CREATE SESSION TO test_schema;
99
- ALTER USER test_schema QUOTA UNLIMITED ON USERS;
114
+ ALTER USER test_schema DEFAULT TABLESPACE USERS;
115
+ GRANT UNLIMITED TABLESPACE TO test_schema;
100
116
 
101
- -- so the test account can build and drop the fixtures inside that schema
117
+ -- so the test account can build and drop the fixtures inside that schema;
118
+ -- SELECT ANY SEQUENCE keeps sequences in test_schema visible to reflection
102
119
  GRANT CREATE ANY TABLE, DROP ANY TABLE, SELECT ANY TABLE, INSERT ANY TABLE,
103
120
  UPDATE ANY TABLE, DELETE ANY TABLE, CREATE ANY INDEX, DROP ANY INDEX,
104
121
  CREATE ANY VIEW, DROP ANY VIEW, CREATE ANY SEQUENCE, DROP ANY SEQUENCE,
105
- COMMENT ANY TABLE, ANALYZE ANY TO <your test user>;
122
+ SELECT ANY SEQUENCE, COMMENT ANY TABLE, ANALYZE ANY TO <your test user>;
106
123
  ```
107
124
 
108
125
  Those `ANY` privileges are broad. They are fine on a throwaway test instance —
@@ -71,16 +71,33 @@ builds tables there.
71
71
 
72
72
  The test account cannot create it (`ORA-01031`), so run this as a DBA once:
73
73
 
74
+ ```
75
+ python test/prepare_test_schema.py --host localhost --service XE \
76
+ --dba-password <SYSTEM password> --test-user <your test user>
77
+ ```
78
+
79
+ It is safe to rerun: each statement is attempted on its own and one that has
80
+ already been applied is reported and skipped. What it runs, for a DBA who would
81
+ rather type it:
82
+
74
83
  ```sql
84
+ -- the test account's own tables must live in USERS: the Oracle dialect hides
85
+ -- SYSTEM-tablespace tables from reflection, and the suite then never empties
86
+ -- them between tests (rows "survive", ORA-00001)
87
+ ALTER USER <your test user> DEFAULT TABLESPACE USERS;
88
+ ALTER USER <your test user> QUOTA UNLIMITED ON USERS;
89
+
75
90
  CREATE USER test_schema IDENTIFIED BY test_schema;
76
91
  GRANT CREATE SESSION TO test_schema;
77
- ALTER USER test_schema QUOTA UNLIMITED ON USERS;
92
+ ALTER USER test_schema DEFAULT TABLESPACE USERS;
93
+ GRANT UNLIMITED TABLESPACE TO test_schema;
78
94
 
79
- -- so the test account can build and drop the fixtures inside that schema
95
+ -- so the test account can build and drop the fixtures inside that schema;
96
+ -- SELECT ANY SEQUENCE keeps sequences in test_schema visible to reflection
80
97
  GRANT CREATE ANY TABLE, DROP ANY TABLE, SELECT ANY TABLE, INSERT ANY TABLE,
81
98
  UPDATE ANY TABLE, DELETE ANY TABLE, CREATE ANY INDEX, DROP ANY INDEX,
82
99
  CREATE ANY VIEW, DROP ANY VIEW, CREATE ANY SEQUENCE, DROP ANY SEQUENCE,
83
- COMMENT ANY TABLE, ANALYZE ANY TO <your test user>;
100
+ SELECT ANY SEQUENCE, COMMENT ANY TABLE, ANALYZE ANY TO <your test user>;
84
101
  ```
85
102
 
86
103
  Those `ANY` privileges are broad. They are fine on a throwaway test instance —
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "sqlalchemy-seerdb"
10
- version = "0.2.0"
10
+ version = "0.2.2"
11
11
  description = "SQLAlchemy dialect for the seerdb driver"
12
12
  readme = "README.md"
13
13
  license = "MIT"
@@ -19,4 +19,4 @@ from sqlalchemy_seerdb.seerdb import SeerdbDialect
19
19
 
20
20
  __all__ = ['SeerdbDialect', '__version__']
21
21
 
22
- __version__ = '0.2.0'
22
+ __version__ = '0.2.2'
@@ -32,7 +32,7 @@ from sqlalchemy.dialects.oracle.base import (
32
32
  OracleDialect,
33
33
  OracleExecutionContext,
34
34
  )
35
- from sqlalchemy.dialects.oracle.types import _OracleDate
35
+ from sqlalchemy.dialects.oracle.types import _OracleBoolean, _OracleDate
36
36
  from sqlalchemy.engine import cursor as _cursor
37
37
  from sqlalchemy.engine import interfaces
38
38
 
@@ -72,6 +72,25 @@ class _SeerdbDate(_OracleDate):
72
72
  return process
73
73
 
74
74
 
75
+ class _SeerdbBoolean(_OracleBoolean):
76
+ """A `Boolean` column read back as a `bool`.
77
+
78
+ Before 23ai there is no BOOLEAN type, so the column is a NUMBER and the
79
+ driver reads 1 or 0 back as a number. From SQLAlchemy 2.1 the generic
80
+ dialect leaves the conversion to an output type handler that only the
81
+ drivers it ships with install, so here it is taken as it is for `Date`. A
82
+ native BOOLEAN already arrives as a `bool` and passes through.
83
+ """
84
+
85
+ def result_processor(self, dialect, coltype):
86
+ def process(value):
87
+ if value is None or isinstance(value, bool):
88
+ return value
89
+ return bool(value)
90
+
91
+ return process
92
+
93
+
75
94
  class SeerdbExecutionContext(OracleExecutionContext):
76
95
  """Turns this backend's RETURNING into the rows SQLAlchemy expects.
77
96
 
@@ -238,6 +257,23 @@ class SeerdbCompiler(OracleCompiler):
238
257
  return super().bindparam_string(name, **kw)
239
258
 
240
259
 
260
+ def _driver_has_fetch_lobs() -> bool:
261
+ """Whether the installed driver understands ``fetch_lobs``.
262
+
263
+ It arrived in seerdb 3.0 together with the LOB object; passing it to an
264
+ older driver is a TypeError, and this dialect supports both."""
265
+ import inspect
266
+
267
+ try:
268
+ params = inspect.signature(seerdb.OracleConnect.__init__).parameters
269
+ except (AttributeError, TypeError, ValueError):
270
+ return False
271
+ return 'fetch_lobs' in params
272
+
273
+
274
+ _DRIVER_HAS_FETCH_LOBS = _driver_has_fetch_lobs()
275
+
276
+
241
277
  class SeerdbDialect(OracleDialect):
242
278
  """SQLAlchemy dialect driving the seerdb DBAPI."""
243
279
 
@@ -271,8 +307,13 @@ class SeerdbDialect(OracleDialect):
271
307
  # no processor either way, so a Float column came back holding a Decimal.
272
308
  supports_native_decimal = True
273
309
 
274
- # As the base dialect's, plus the date narrowing above.
275
- colspecs: ClassVar[dict] = {**_oracle_base.colspecs, sqltypes.Date: _SeerdbDate}
310
+ # As the base dialect's, plus the date narrowing and the boolean
311
+ # conversion above.
312
+ colspecs: ClassVar[dict] = {
313
+ **_oracle_base.colspecs,
314
+ sqltypes.Date: _SeerdbDate,
315
+ sqltypes.Boolean: _SeerdbBoolean,
316
+ }
276
317
 
277
318
  # No SQL is generated differently from the base dialect, so cached
278
319
  # statements stay valid.
@@ -347,6 +388,17 @@ class SeerdbDialect(OracleDialect):
347
388
  # DBAPI contract SQLAlchemy builds on is autocommit off, so that is the
348
389
  # default here; ``?autocommit=true`` in the URL still turns it on.
349
390
  options['autocommit'] = False
391
+ # SQLAlchemy's type machinery expects a CLOB / BLOB column to arrive as
392
+ # str / bytes -- its Text and LargeBinary result processors, and every
393
+ # comparison the compliance suite makes, are written against values.
394
+ # seerdb 3.0 returns a LOB OBJECT by default instead (matching
395
+ # python-oracledb), so ask for the values this dialect is built on.
396
+ #
397
+ # Feature-detected rather than pinned: the same dialect still has to
398
+ # work against a driver that predates the option, and its floor cannot
399
+ # be raised to a release that is not published yet.
400
+ if _DRIVER_HAS_FETCH_LOBS:
401
+ options['fetch_lobs'] = False
350
402
  for key, value in url.query.items():
351
403
  # A repeated key arrives as a tuple; the driver takes one value.
352
404
  options[key] = _coerce(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-seerdb
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: SQLAlchemy dialect for the seerdb driver
5
5
  Author-email: Peter Lemenkov <lemenkov@gmail.com>
6
6
  License-Expression: MIT
@@ -93,16 +93,33 @@ builds tables there.
93
93
 
94
94
  The test account cannot create it (`ORA-01031`), so run this as a DBA once:
95
95
 
96
+ ```
97
+ python test/prepare_test_schema.py --host localhost --service XE \
98
+ --dba-password <SYSTEM password> --test-user <your test user>
99
+ ```
100
+
101
+ It is safe to rerun: each statement is attempted on its own and one that has
102
+ already been applied is reported and skipped. What it runs, for a DBA who would
103
+ rather type it:
104
+
96
105
  ```sql
106
+ -- the test account's own tables must live in USERS: the Oracle dialect hides
107
+ -- SYSTEM-tablespace tables from reflection, and the suite then never empties
108
+ -- them between tests (rows "survive", ORA-00001)
109
+ ALTER USER <your test user> DEFAULT TABLESPACE USERS;
110
+ ALTER USER <your test user> QUOTA UNLIMITED ON USERS;
111
+
97
112
  CREATE USER test_schema IDENTIFIED BY test_schema;
98
113
  GRANT CREATE SESSION TO test_schema;
99
- ALTER USER test_schema QUOTA UNLIMITED ON USERS;
114
+ ALTER USER test_schema DEFAULT TABLESPACE USERS;
115
+ GRANT UNLIMITED TABLESPACE TO test_schema;
100
116
 
101
- -- so the test account can build and drop the fixtures inside that schema
117
+ -- so the test account can build and drop the fixtures inside that schema;
118
+ -- SELECT ANY SEQUENCE keeps sequences in test_schema visible to reflection
102
119
  GRANT CREATE ANY TABLE, DROP ANY TABLE, SELECT ANY TABLE, INSERT ANY TABLE,
103
120
  UPDATE ANY TABLE, DELETE ANY TABLE, CREATE ANY INDEX, DROP ANY INDEX,
104
121
  CREATE ANY VIEW, DROP ANY VIEW, CREATE ANY SEQUENCE, DROP ANY SEQUENCE,
105
- COMMENT ANY TABLE, ANALYZE ANY TO <your test user>;
122
+ SELECT ANY SEQUENCE, COMMENT ANY TABLE, ANALYZE ANY TO <your test user>;
106
123
  ```
107
124
 
108
125
  Those `ANY` privileges are broad. They are fine on a throwaway test instance —
@@ -11,6 +11,7 @@ sqlalchemy_seerdb.egg-info/dependency_links.txt
11
11
  sqlalchemy_seerdb.egg-info/entry_points.txt
12
12
  sqlalchemy_seerdb.egg-info/requires.txt
13
13
  sqlalchemy_seerdb.egg-info/top_level.txt
14
+ test/test_boolean.py
14
15
  test/test_connect_args.py
15
16
  test/test_suite.py
16
17
  test/test_version.py
@@ -0,0 +1,32 @@
1
+ # SPDX-FileCopyrightText: 2026 Peter Lemenkov <lemenkov@gmail.com>
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """A Boolean column comes back as a bool whatever the driver returns."""
5
+
6
+ import unittest
7
+
8
+ from sqlalchemy import types as sqltypes
9
+
10
+ from sqlalchemy_seerdb.seerdb import SeerdbDialect, _SeerdbBoolean
11
+
12
+
13
+ class TestBoolean(unittest.TestCase):
14
+ def setUp(self):
15
+ self.process = _SeerdbBoolean().result_processor(SeerdbDialect(), None)
16
+
17
+ def test_a_number_becomes_a_bool(self):
18
+ # Before 23ai the column is a NUMBER, and the driver reads 1 / 0.
19
+ self.assertIs(self.process(1), True)
20
+ self.assertIs(self.process(0), False)
21
+
22
+ def test_a_native_bool_and_a_null_pass_through(self):
23
+ self.assertIs(self.process(True), True)
24
+ self.assertIs(self.process(False), False)
25
+ self.assertIsNone(self.process(None))
26
+
27
+ def test_the_dialect_uses_it_for_boolean(self):
28
+ self.assertIs(SeerdbDialect.colspecs[sqltypes.Boolean], _SeerdbBoolean)
29
+
30
+
31
+ if __name__ == '__main__':
32
+ unittest.main()