alt-python-pynosqlc-cassandra 1.0.4__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.
@@ -0,0 +1,30 @@
1
+
2
+ # ── GSD baseline (auto-generated) ──
3
+ .gsd
4
+ .DS_Store
5
+ Thumbs.db
6
+ *.swp
7
+ *.swo
8
+ *~
9
+ .idea/
10
+ .vscode/
11
+ *.code-workspace
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+ node_modules/
16
+ .next/
17
+ dist/
18
+ build/
19
+ __pycache__/
20
+ *.pyc
21
+ .venv/
22
+ venv/
23
+ target/
24
+ vendor/
25
+ *.log
26
+ coverage/
27
+ .cache/
28
+ tmp/
29
+
30
+ /.bg-shell/
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: alt-python-pynosqlc-cassandra
3
+ Version: 1.0.4
4
+ Summary: Cassandra driver for pynosqlc
5
+ Project-URL: Homepage, https://github.com/alt-python/pynosqlc
6
+ Project-URL: Repository, https://github.com/alt-python/pynosqlc
7
+ Project-URL: Documentation, https://github.com/alt-python/pynosqlc#getting-started
8
+ Project-URL: Bug Tracker, https://github.com/alt-python/pynosqlc/issues
9
+ Author: Craig Parravicini, Claude (Anthropic)
10
+ License: MIT
11
+ Keywords: async,cassandra,database,driver,nosql
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Database
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: alt-python-pynosqlc-core
22
+ Requires-Dist: alt-python-pynosqlc-memory
23
+ Requires-Dist: cassandra-driver>=3.29.1
24
+ Description-Content-Type: text/markdown
25
+
26
+ # pynosqlc-cassandra
27
+
28
+ Cassandra 4 driver for [pynosqlc](https://github.com/alt-python/pynosqlc) — a
29
+ JDBC-inspired unified async NoSQL access layer for Python.
30
+
31
+ Install this driver to connect pynosqlc to a Cassandra 4 instance using the
32
+ cassandra-driver library, bridged into Python's asyncio via
33
+ `asyncio.run_in_executor`. All pynosqlc operations — `store`, `get`, `insert`,
34
+ `update`, `delete`, and `find` — are supported.
35
+
36
+ ## Requirements
37
+
38
+ - Python 3.12+
39
+ - Cassandra 4.0+ instance (local or remote)
40
+ - `pynosqlc-core` and `pynosqlc-memory` (installed automatically as dependencies)
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install alt-python-pynosqlc-cassandra
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ import asyncio
52
+ from pynosqlc.core import DriverManager, Filter
53
+ import pynosqlc.cassandra # auto-registers CassandraDriver on import
54
+
55
+ async def main():
56
+ async with await DriverManager.get_client(
57
+ 'pynosqlc:cassandra:localhost:9042/my_keyspace'
58
+ ) as client:
59
+ col = client.get_collection('orders')
60
+
61
+ # Store a document at a known key (upsert semantics)
62
+ await col.store('order-001', {'item': 'widget', 'qty': 5, 'status': 'pending'})
63
+
64
+ # Retrieve a document by key
65
+ doc = await col.get('order-001')
66
+ print(doc) # {'item': 'widget', 'qty': 5, 'status': 'pending', '_id': 'order-001'}
67
+
68
+ # Insert a document with a driver-assigned key
69
+ key = await col.insert({'item': 'gadget', 'qty': 2, 'status': 'pending'})
70
+ print(key) # e.g. 'a1b2c3d4-...'
71
+
72
+ # Update fields (shallow merge — only listed fields change)
73
+ await col.update('order-001', {'qty': 10, 'status': 'shipped'})
74
+
75
+ # Find documents matching a filter
76
+ f = Filter.where('status').eq('pending').build()
77
+ async for doc in await col.find(f):
78
+ print(doc)
79
+
80
+ # Delete a document
81
+ await col.delete('order-001')
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ ## URL Scheme
87
+
88
+ ```
89
+ pynosqlc:cassandra:<host>:<port>/<keyspace>
90
+ ```
91
+
92
+ | URL | Description |
93
+ |-----|-------------|
94
+ | `pynosqlc:cassandra:localhost:9042/my_keyspace` | Local Cassandra, named keyspace |
95
+ | `pynosqlc:cassandra:cassandra.example.com:9042/prod` | Remote instance |
96
+
97
+ The `<keyspace>` segment is required. If the keyspace does not exist, the driver
98
+ creates it automatically using `SimpleStrategy` with replication factor 1. For
99
+ production use, create the keyspace manually with appropriate replication
100
+ settings before connecting.
101
+
102
+ ## Schema
103
+
104
+ Each pynosqlc collection maps to a Cassandra table in the configured keyspace:
105
+
106
+ ```cql
107
+ CREATE TABLE IF NOT EXISTS <collection_name> (
108
+ pk TEXT PRIMARY KEY,
109
+ data TEXT
110
+ );
111
+ ```
112
+
113
+ - `pk` — the document key (string)
114
+ - `data` — the document serialised as a JSON string
115
+
116
+ Tables are created automatically on the first operation against a collection.
117
+ You do not need to create tables manually.
118
+
119
+ ## Filtering
120
+
121
+ Filters are evaluated **in-process** after a full table scan. `find()` fetches
122
+ every row from the collection table, deserialises the `data` column, then
123
+ applies the pynosqlc filter AST in memory using `MemoryFilterEvaluator`. There
124
+ is no CQL WHERE clause generated.
125
+
126
+ This matches the design of the jsnosqlc Cassandra driver and is appropriate for
127
+ development, testing, and moderate-sized collections. For production workloads
128
+ with large tables, evaluate CQL secondary indexes or materialised views as
129
+ complementary tools.
130
+
131
+ All filter operators are supported: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`,
132
+ `contains`, `in_`, `nin`, `exists`, and their `and_` / `or_` / `not_`
133
+ combinators.
134
+
135
+ ## Async Integration
136
+
137
+ cassandra-driver uses a synchronous API. This driver wraps every blocking CQL
138
+ call in `asyncio.get_event_loop().run_in_executor(None, ...)` so the asyncio
139
+ event loop is never blocked. The default cassandra-driver reactor
140
+ (thread-based) is used. Do not set `connection_class=AsyncioConnection` —
141
+ `AsyncioConnection` hooks into the event loop from the main thread and cannot
142
+ be used from a thread-pool executor.
143
+
144
+ ## Troubleshooting
145
+
146
+ **`NoHostAvailable` or `ConnectionException` on connect**
147
+ Cassandra is not running or is not reachable on the configured host and port.
148
+ Verify with `cqlsh <host> <port>` — you should reach the CQL shell prompt.
149
+
150
+ **`ImportError: No module named 'pynosqlc.cassandra'`**
151
+ The package is not installed. Run `pip install alt-python-pynosqlc-cassandra`.
152
+
153
+ **`ValueError: No driver found for URL ...`**
154
+ The import `import pynosqlc.cassandra` was not executed before calling
155
+ `DriverManager.get_client(...)`. The import is what triggers driver
156
+ registration — it must come before any `get_client` call.
157
+
158
+ **`InvalidRequest: Keyspace '<name>' does not exist`**
159
+ This should not occur in normal use because the driver creates the keyspace
160
+ automatically. If you see this error, check that the connecting user has
161
+ `CREATE KEYSPACE` permission, or create the keyspace manually:
162
+
163
+ ```cql
164
+ CREATE KEYSPACE my_keyspace
165
+ WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};
166
+ ```
167
+
168
+ **Filter returns no results despite documents being present**
169
+ Confirm you called `.build()` at the end of your filter chain:
170
+ `Filter.where('field').eq('value').build()`. Passing an unbuilt `FieldCondition`
171
+ object instead of the built `dict` will match nothing.
172
+
173
+ ## Further Reading
174
+
175
+ - [pynosqlc API reference](../../docs/api-reference.md) — complete method signatures
176
+ - [Driver implementation guide](../../docs/driver-guide.md) — how pynosqlc drivers work
177
+ - [Getting started tutorial](../../docs/getting-started.md) — step-by-step introduction
@@ -0,0 +1,152 @@
1
+ # pynosqlc-cassandra
2
+
3
+ Cassandra 4 driver for [pynosqlc](https://github.com/alt-python/pynosqlc) — a
4
+ JDBC-inspired unified async NoSQL access layer for Python.
5
+
6
+ Install this driver to connect pynosqlc to a Cassandra 4 instance using the
7
+ cassandra-driver library, bridged into Python's asyncio via
8
+ `asyncio.run_in_executor`. All pynosqlc operations — `store`, `get`, `insert`,
9
+ `update`, `delete`, and `find` — are supported.
10
+
11
+ ## Requirements
12
+
13
+ - Python 3.12+
14
+ - Cassandra 4.0+ instance (local or remote)
15
+ - `pynosqlc-core` and `pynosqlc-memory` (installed automatically as dependencies)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install alt-python-pynosqlc-cassandra
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ import asyncio
27
+ from pynosqlc.core import DriverManager, Filter
28
+ import pynosqlc.cassandra # auto-registers CassandraDriver on import
29
+
30
+ async def main():
31
+ async with await DriverManager.get_client(
32
+ 'pynosqlc:cassandra:localhost:9042/my_keyspace'
33
+ ) as client:
34
+ col = client.get_collection('orders')
35
+
36
+ # Store a document at a known key (upsert semantics)
37
+ await col.store('order-001', {'item': 'widget', 'qty': 5, 'status': 'pending'})
38
+
39
+ # Retrieve a document by key
40
+ doc = await col.get('order-001')
41
+ print(doc) # {'item': 'widget', 'qty': 5, 'status': 'pending', '_id': 'order-001'}
42
+
43
+ # Insert a document with a driver-assigned key
44
+ key = await col.insert({'item': 'gadget', 'qty': 2, 'status': 'pending'})
45
+ print(key) # e.g. 'a1b2c3d4-...'
46
+
47
+ # Update fields (shallow merge — only listed fields change)
48
+ await col.update('order-001', {'qty': 10, 'status': 'shipped'})
49
+
50
+ # Find documents matching a filter
51
+ f = Filter.where('status').eq('pending').build()
52
+ async for doc in await col.find(f):
53
+ print(doc)
54
+
55
+ # Delete a document
56
+ await col.delete('order-001')
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ## URL Scheme
62
+
63
+ ```
64
+ pynosqlc:cassandra:<host>:<port>/<keyspace>
65
+ ```
66
+
67
+ | URL | Description |
68
+ |-----|-------------|
69
+ | `pynosqlc:cassandra:localhost:9042/my_keyspace` | Local Cassandra, named keyspace |
70
+ | `pynosqlc:cassandra:cassandra.example.com:9042/prod` | Remote instance |
71
+
72
+ The `<keyspace>` segment is required. If the keyspace does not exist, the driver
73
+ creates it automatically using `SimpleStrategy` with replication factor 1. For
74
+ production use, create the keyspace manually with appropriate replication
75
+ settings before connecting.
76
+
77
+ ## Schema
78
+
79
+ Each pynosqlc collection maps to a Cassandra table in the configured keyspace:
80
+
81
+ ```cql
82
+ CREATE TABLE IF NOT EXISTS <collection_name> (
83
+ pk TEXT PRIMARY KEY,
84
+ data TEXT
85
+ );
86
+ ```
87
+
88
+ - `pk` — the document key (string)
89
+ - `data` — the document serialised as a JSON string
90
+
91
+ Tables are created automatically on the first operation against a collection.
92
+ You do not need to create tables manually.
93
+
94
+ ## Filtering
95
+
96
+ Filters are evaluated **in-process** after a full table scan. `find()` fetches
97
+ every row from the collection table, deserialises the `data` column, then
98
+ applies the pynosqlc filter AST in memory using `MemoryFilterEvaluator`. There
99
+ is no CQL WHERE clause generated.
100
+
101
+ This matches the design of the jsnosqlc Cassandra driver and is appropriate for
102
+ development, testing, and moderate-sized collections. For production workloads
103
+ with large tables, evaluate CQL secondary indexes or materialised views as
104
+ complementary tools.
105
+
106
+ All filter operators are supported: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`,
107
+ `contains`, `in_`, `nin`, `exists`, and their `and_` / `or_` / `not_`
108
+ combinators.
109
+
110
+ ## Async Integration
111
+
112
+ cassandra-driver uses a synchronous API. This driver wraps every blocking CQL
113
+ call in `asyncio.get_event_loop().run_in_executor(None, ...)` so the asyncio
114
+ event loop is never blocked. The default cassandra-driver reactor
115
+ (thread-based) is used. Do not set `connection_class=AsyncioConnection` —
116
+ `AsyncioConnection` hooks into the event loop from the main thread and cannot
117
+ be used from a thread-pool executor.
118
+
119
+ ## Troubleshooting
120
+
121
+ **`NoHostAvailable` or `ConnectionException` on connect**
122
+ Cassandra is not running or is not reachable on the configured host and port.
123
+ Verify with `cqlsh <host> <port>` — you should reach the CQL shell prompt.
124
+
125
+ **`ImportError: No module named 'pynosqlc.cassandra'`**
126
+ The package is not installed. Run `pip install alt-python-pynosqlc-cassandra`.
127
+
128
+ **`ValueError: No driver found for URL ...`**
129
+ The import `import pynosqlc.cassandra` was not executed before calling
130
+ `DriverManager.get_client(...)`. The import is what triggers driver
131
+ registration — it must come before any `get_client` call.
132
+
133
+ **`InvalidRequest: Keyspace '<name>' does not exist`**
134
+ This should not occur in normal use because the driver creates the keyspace
135
+ automatically. If you see this error, check that the connecting user has
136
+ `CREATE KEYSPACE` permission, or create the keyspace manually:
137
+
138
+ ```cql
139
+ CREATE KEYSPACE my_keyspace
140
+ WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};
141
+ ```
142
+
143
+ **Filter returns no results despite documents being present**
144
+ Confirm you called `.build()` at the end of your filter chain:
145
+ `Filter.where('field').eq('value').build()`. Passing an unbuilt `FieldCondition`
146
+ object instead of the built `dict` will match nothing.
147
+
148
+ ## Further Reading
149
+
150
+ - [pynosqlc API reference](../../docs/api-reference.md) — complete method signatures
151
+ - [Driver implementation guide](../../docs/driver-guide.md) — how pynosqlc drivers work
152
+ - [Getting started tutorial](../../docs/getting-started.md) — step-by-step introduction
@@ -0,0 +1,17 @@
1
+ """
2
+ pynosqlc.cassandra — Cassandra driver for pynosqlc.
3
+
4
+ Handles URLs of the form: pynosqlc:cassandra:host:port/keyspace
5
+
6
+ Auto-registers ``CassandraDriver`` with ``DriverManager`` on import via
7
+ the ``cassandra_driver`` module.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pynosqlc.cassandra import cassandra_driver # noqa: F401
13
+ from pynosqlc.cassandra.cassandra_client import CassandraClient
14
+ from pynosqlc.cassandra.cassandra_collection import CassandraCollection
15
+ from pynosqlc.cassandra.cassandra_driver import CassandraDriver
16
+
17
+ __all__ = ["CassandraDriver", "CassandraClient", "CassandraCollection"]
@@ -0,0 +1,49 @@
1
+ """
2
+ cassandra_client.py — Cassandra Client implementation.
3
+
4
+ Each collection is created on demand; the parent Client base class
5
+ caches by name via get_collection().
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from typing import TYPE_CHECKING
12
+
13
+ from pynosqlc.core.client import Client
14
+ from pynosqlc.cassandra.cassandra_collection import CassandraCollection
15
+
16
+ if TYPE_CHECKING:
17
+ from cassandra.cluster import Cluster, Session
18
+
19
+
20
+ class CassandraClient(Client):
21
+ """Client backed by a cassandra-driver synchronous session.
22
+
23
+ Args:
24
+ url: the pynosqlc URL used to open this connection
25
+ cluster: the connected ``Cluster`` instance (for shutdown)
26
+ session: the connected ``Session`` instance
27
+ keyspace: the active Cassandra keyspace
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ url: str,
33
+ cluster: "Cluster",
34
+ session: "Session",
35
+ keyspace: str,
36
+ ) -> None:
37
+ super().__init__({"url": url})
38
+ self._cluster = cluster
39
+ self._session = session
40
+ self._keyspace = keyspace
41
+
42
+ def _get_collection(self, name: str) -> CassandraCollection:
43
+ """Create and return a :class:`CassandraCollection` for *name*."""
44
+ return CassandraCollection(self, name, self._session)
45
+
46
+ async def _close(self) -> None:
47
+ """Shut down the underlying Cassandra cluster connection."""
48
+ loop = asyncio.get_event_loop()
49
+ await loop.run_in_executor(None, self._cluster.shutdown)
@@ -0,0 +1,113 @@
1
+ """
2
+ cassandra_collection.py — Cassandra Collection implementation.
3
+
4
+ Storage layout
5
+ --------------
6
+ Each pynosqlc collection maps to one Cassandra table:
7
+
8
+ CREATE TABLE IF NOT EXISTS <name> (
9
+ pk TEXT PRIMARY KEY,
10
+ data TEXT
11
+ )
12
+
13
+ Documents are stored as JSON in the ``data`` column. Filtering is
14
+ performed in-process using :class:`MemoryFilterEvaluator` after a full
15
+ table scan — appropriate for test/dev workloads.
16
+
17
+ All session.execute() calls are dispatched via run_in_executor so they
18
+ don't block the asyncio event loop.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import asyncio
24
+ import json
25
+ import uuid
26
+ from typing import TYPE_CHECKING
27
+
28
+ from pynosqlc.core.collection import Collection
29
+ from pynosqlc.core.cursor import Cursor
30
+ from pynosqlc.memory.memory_filter_evaluator import MemoryFilterEvaluator
31
+
32
+ if TYPE_CHECKING:
33
+ from cassandra.cluster import Session
34
+
35
+ from pynosqlc.core.client import Client
36
+
37
+
38
+ class CassandraCollection(Collection):
39
+ """Collection backed by a Cassandra table (pk TEXT, data TEXT).
40
+
41
+ Args:
42
+ client: the owning :class:`CassandraClient`
43
+ name: collection name (used as the CQL table name)
44
+ session: the shared cassandra-driver ``Session``
45
+ """
46
+
47
+ def __init__(self, client: "Client", name: str, session: "Session") -> None:
48
+ super().__init__(client, name)
49
+ self._session = session
50
+ self._table_ready: bool = False
51
+
52
+ # ── Table bootstrap ────────────────────────────────────────────────────
53
+
54
+ async def _ensure_table(self) -> None:
55
+ """Create the backing table if it does not yet exist."""
56
+ if self._table_ready:
57
+ return
58
+ loop = asyncio.get_event_loop()
59
+ cql = (
60
+ f"CREATE TABLE IF NOT EXISTS {self._name} ("
61
+ f"pk TEXT PRIMARY KEY, data TEXT)"
62
+ )
63
+ await loop.run_in_executor(None, self._session.execute, cql)
64
+ self._table_ready = True
65
+
66
+ # ── Abstract implementation hooks ──────────────────────────────────────
67
+
68
+ async def _get(self, key: str) -> dict | None:
69
+ await self._ensure_table()
70
+ loop = asyncio.get_event_loop()
71
+ cql = f"SELECT data FROM {self._name} WHERE pk = %s"
72
+ rows = await loop.run_in_executor(None, self._session.execute, cql, (key,))
73
+ row = rows.one()
74
+ return json.loads(row["data"]) if row is not None else None
75
+
76
+ async def _store(self, key: str, doc: dict) -> None:
77
+ await self._ensure_table()
78
+ loop = asyncio.get_event_loop()
79
+ cql = f"INSERT INTO {self._name} (pk, data) VALUES (%s, %s)"
80
+ await loop.run_in_executor(
81
+ None, self._session.execute, cql, (key, json.dumps(doc))
82
+ )
83
+
84
+ async def _delete(self, key: str) -> None:
85
+ await self._ensure_table()
86
+ loop = asyncio.get_event_loop()
87
+ cql = f"DELETE FROM {self._name} WHERE pk = %s"
88
+ await loop.run_in_executor(None, self._session.execute, cql, (key,))
89
+
90
+ async def _insert(self, doc: dict) -> str:
91
+ key = str(uuid.uuid4())
92
+ await self._store(key, {**doc, "_id": key})
93
+ return key
94
+
95
+ async def _update(self, key: str, patch: dict) -> None:
96
+ existing = await self._get(key)
97
+ if existing is None:
98
+ raise KeyError(f"Document not found for key: {key!r}")
99
+ await self._store(key, {**existing, **patch})
100
+
101
+ async def _find(self, ast: dict) -> Cursor:
102
+ await self._ensure_table()
103
+ loop = asyncio.get_event_loop()
104
+ cql = f"SELECT pk, data FROM {self._name}"
105
+ rows = await loop.run_in_executor(None, self._session.execute, cql)
106
+
107
+ results = []
108
+ for row in rows:
109
+ doc = json.loads(row["data"])
110
+ if MemoryFilterEvaluator.matches(doc, ast):
111
+ results.append(doc)
112
+
113
+ return Cursor(results)
@@ -0,0 +1,94 @@
1
+ """
2
+ cassandra_driver.py — Cassandra pynosqlc driver.
3
+
4
+ Handles URL: pynosqlc:cassandra:host:port/keyspace
5
+ Auto-registers with DriverManager on import.
6
+
7
+ cassandra-driver uses a synchronous API; all blocking calls are executed
8
+ via asyncio.get_event_loop().run_in_executor(None, ...) so they don't
9
+ block the event loop.
10
+
11
+ The default LibevConnection / ThreadedRequestExecutor reactor is used
12
+ (not AsyncioConnection) because AsyncioConnection cannot hook into the
13
+ running event loop when invoked from run_in_executor's thread-pool thread.
14
+ The standard reactor works correctly from any thread.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+
21
+ from pynosqlc.core.driver import Driver
22
+ from pynosqlc.core.driver_manager import DriverManager
23
+ from pynosqlc.cassandra.cassandra_client import CassandraClient
24
+
25
+
26
+ class CassandraDriver(Driver):
27
+ """Driver that creates :class:`CassandraClient` instances.
28
+
29
+ URL format: ``pynosqlc:cassandra:host:port/keyspace``
30
+ Default port: 9042
31
+ Default keyspace: ``pynosqlc``
32
+ """
33
+
34
+ URL_PREFIX: str = "pynosqlc:cassandra:"
35
+
36
+ def accepts_url(self, url: str) -> bool:
37
+ """Return ``True`` for ``'pynosqlc:cassandra:...'`` URLs."""
38
+ return isinstance(url, str) and url.startswith(self.URL_PREFIX)
39
+
40
+ async def connect(
41
+ self,
42
+ url: str,
43
+ properties: dict | None = None,
44
+ ) -> CassandraClient:
45
+ """Parse URL, connect, and return a :class:`CassandraClient`.
46
+
47
+ URL format: ``pynosqlc:cassandra:host:port/keyspace``
48
+ Example: ``pynosqlc:cassandra:localhost:9042/pynosqlc_test``
49
+
50
+ Connection errors from cassandra-driver propagate directly —
51
+ ``NoHostAvailable`` is the expected signal when Cassandra is absent
52
+ (used by compliance tests to skip).
53
+ """
54
+ from cassandra.cluster import Cluster
55
+ from cassandra.query import dict_factory
56
+
57
+ # Parse: strip prefix → "host:port/keyspace"
58
+ tail = url[len(self.URL_PREFIX):]
59
+ if "/" in tail:
60
+ host_port, keyspace = tail.split("/", 1)
61
+ else:
62
+ host_port, keyspace = tail, "pynosqlc"
63
+
64
+ if ":" in host_port:
65
+ host, port_str = host_port.rsplit(":", 1)
66
+ port = int(port_str)
67
+ else:
68
+ host = host_port
69
+ port = 9042
70
+
71
+ loop = asyncio.get_event_loop()
72
+
73
+ cluster = Cluster(
74
+ contact_points=[host],
75
+ port=port,
76
+ )
77
+ session = await loop.run_in_executor(None, lambda: cluster.connect())
78
+ await loop.run_in_executor(
79
+ None,
80
+ session.execute,
81
+ (
82
+ f"CREATE KEYSPACE IF NOT EXISTS {keyspace} "
83
+ f"WITH REPLICATION = {{'class': 'SimpleStrategy', 'replication_factor': 1}}"
84
+ ),
85
+ )
86
+ await loop.run_in_executor(None, session.set_keyspace, keyspace)
87
+ session.row_factory = dict_factory
88
+
89
+ return CassandraClient(url, cluster, session, keyspace)
90
+
91
+
92
+ # Auto-register on import — a single shared instance is sufficient.
93
+ _driver = CassandraDriver()
94
+ DriverManager.register_driver(_driver)
@@ -0,0 +1,49 @@
1
+ [project]
2
+ name = "alt-python-pynosqlc-cassandra"
3
+ version = "1.0.4"
4
+ description = "Cassandra driver for pynosqlc"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "alt-python-pynosqlc-core",
9
+ "alt-python-pynosqlc-memory",
10
+ "cassandra-driver>=3.29.1",
11
+ ]
12
+ authors = [
13
+ {name = "Craig Parravicini"},
14
+ {name = "Claude (Anthropic)"},
15
+ ]
16
+ license = {text = "MIT"}
17
+ keywords = ["nosql", "database", "async", "cassandra", "driver"]
18
+ classifiers = [
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Framework :: AsyncIO",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Database",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/alt-python/pynosqlc"
31
+ Repository = "https://github.com/alt-python/pynosqlc"
32
+ Documentation = "https://github.com/alt-python/pynosqlc#getting-started"
33
+ "Bug Tracker" = "https://github.com/alt-python/pynosqlc/issues"
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["pynosqlc"]
41
+
42
+ [tool.uv.sources]
43
+ alt-python-pynosqlc-core = { workspace = true }
44
+ alt-python-pynosqlc-memory = { workspace = true }
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ asyncio_mode = "auto"
49
+ addopts = "--import-mode=importlib"
File without changes
@@ -0,0 +1,66 @@
1
+ """
2
+ test_compliance.py — Cassandra driver compliance tests.
3
+
4
+ Wires the shared pynosqlc.core compliance suite into the cassandra package.
5
+ Each test run gets a fresh CassandraClient connected to a live Cassandra 4 instance.
6
+
7
+ Set CASSANDRA_URL to override the default URL
8
+ (default: pynosqlc:cassandra:localhost:9042/pynosqlc_test).
9
+ Tests are skipped automatically if Cassandra is not reachable.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import os
16
+
17
+ import pytest
18
+
19
+ from pynosqlc.core import DriverManager
20
+ from pynosqlc.core.testing import run_compliance
21
+ import pynosqlc.cassandra # noqa: F401 — registers CassandraDriver on import
22
+ from pynosqlc.cassandra.cassandra_driver import _driver
23
+
24
+ CASSANDRA_URL = os.environ.get(
25
+ "CASSANDRA_URL", "pynosqlc:cassandra:localhost:9042/pynosqlc_test"
26
+ )
27
+
28
+
29
+ async def _factory():
30
+ """Return a fresh, open CassandraClient for each test class fixture.
31
+
32
+ Clears and re-registers the driver, connects to Cassandra, drops and
33
+ recreates the keyspace so each test class starts with a clean slate,
34
+ and returns the client.
35
+
36
+ Skips the test if Cassandra is not reachable.
37
+ """
38
+ DriverManager.clear()
39
+ DriverManager.register_driver(_driver)
40
+
41
+ try:
42
+ client = await DriverManager.get_client(CASSANDRA_URL)
43
+ # Drop and recreate keyspace for a clean slate between test classes
44
+ loop = asyncio.get_event_loop()
45
+ keyspace = "pynosqlc_test"
46
+ await loop.run_in_executor(
47
+ None,
48
+ client._session.execute,
49
+ f"DROP KEYSPACE IF EXISTS {keyspace}",
50
+ )
51
+ await loop.run_in_executor(
52
+ None,
53
+ client._session.execute,
54
+ (
55
+ f"CREATE KEYSPACE IF NOT EXISTS {keyspace} "
56
+ f"WITH REPLICATION = {{'class': 'SimpleStrategy', 'replication_factor': 1}}"
57
+ ),
58
+ )
59
+ await loop.run_in_executor(None, client._session.set_keyspace, keyspace)
60
+ except Exception as e:
61
+ pytest.skip(f"Cassandra not available: {e}")
62
+
63
+ return client
64
+
65
+
66
+ run_compliance(_factory)