rhosocial-activerecord-postgres 1.0.0.dev2__tar.gz → 1.0.0.dev4__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.
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/PKG-INFO +187 -353
- rhosocial_activerecord_postgres-1.0.0.dev4/README.md +293 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/pyproject.toml +3 -2
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/__init__.py +346 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial/activerecord/backend/impl/postgres/__main__.py +2 -2
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/__init__.py +117 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/base.py +215 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/bit_string.py +124 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/geometric.py +162 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/json.py +195 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/monetary.py +204 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/network_address.py +297 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/object_identifier.py +411 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/pg_lsn.py +132 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/range.py +208 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/text_search.py +229 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/adapters/xml.py +134 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/backend/__init__.py +21 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/backend/async_backend.py +605 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/backend/base.py +193 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/backend/sync.py +606 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial/activerecord/backend/impl/postgres/config.py +2 -1
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/dialect.py +845 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/functions/__init__.py +178 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/functions/bit_string.py +212 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/functions/enum.py +160 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/functions/geometric.py +225 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/functions/json.py +286 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/functions/range.py +379 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/mixins.py +1004 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/protocols.py +1414 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/statements.py +405 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial/activerecord/backend/impl/postgres/transaction.py +175 -5
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/type_compatibility.py +456 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/__init__.py +262 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/bit_string.py +123 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/constants.py +275 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/enum.py +366 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/geometric.py +385 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/json.py +197 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/monetary.py +137 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/network_address.py +233 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/object_identifier.py +634 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/pg_lsn.py +215 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/range.py +577 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/text_search.py +1099 -0
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial/activerecord/backend/impl/postgres/types/xml.py +152 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial_activerecord_postgres.egg-info/PKG-INFO +187 -353
- rhosocial_activerecord_postgres-1.0.0.dev4/src/rhosocial_activerecord_postgres.egg-info/SOURCES.txt +54 -0
- rhosocial_activerecord_postgres-1.0.0.dev2/README.md +0 -459
- rhosocial_activerecord_postgres-1.0.0.dev2/src/rhosocial/activerecord/backend/impl/postgres/__init__.py +0 -76
- rhosocial_activerecord_postgres-1.0.0.dev2/src/rhosocial/activerecord/backend/impl/postgres/adapters.py +0 -71
- rhosocial_activerecord_postgres-1.0.0.dev2/src/rhosocial/activerecord/backend/impl/postgres/backend.py +0 -822
- rhosocial_activerecord_postgres-1.0.0.dev2/src/rhosocial/activerecord/backend/impl/postgres/dialect.py +0 -1076
- rhosocial_activerecord_postgres-1.0.0.dev2/src/rhosocial/activerecord/backend/impl/postgres/types.py +0 -220
- rhosocial_activerecord_postgres-1.0.0.dev2/src/rhosocial_activerecord_postgres.egg-info/SOURCES.txt +0 -18
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/LICENSE +0 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/MANIFEST.in +0 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/setup.cfg +0 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial/activerecord/backend/impl/postgres/README.md +0 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial_activerecord_postgres.egg-info/dependency_links.txt +0 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial_activerecord_postgres.egg-info/requires.txt +0 -0
- {rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/src/rhosocial_activerecord_postgres.egg-info/top_level.txt +0 -0
{rhosocial_activerecord_postgres-1.0.0.dev2 → rhosocial_activerecord_postgres-1.0.0.dev4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rhosocial_activerecord_postgres
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev4
|
|
4
4
|
Summary: postgres backend implementation for rhosocial-activerecord, providing a robust and optimized postgres database support.
|
|
5
5
|
Author-email: vistart <i@vistart.me>
|
|
6
6
|
License: Apache License
|
|
@@ -255,456 +255,290 @@ Dynamic: license-file
|
|
|
255
255
|
[](https://pypi.org/project/rhosocial-activerecord-postgres/)
|
|
256
256
|
[](https://github.com/rhosocial/python-activerecord-postgres/actions)
|
|
257
257
|
[](https://app.codecov.io/gh/rhosocial/python-activerecord-postgres/tree/main)
|
|
258
|
-
[](https://github.com/rhosocial/python-activerecord-postgres/blob/main/LICENSE)
|
|
258
|
+
[](https://github.com/rhosocial/python-activerecord-postgres/blob/main/LICENSE)
|
|
259
259
|
[](https://github.com/vistart)
|
|
260
260
|
|
|
261
261
|
<div align="center">
|
|
262
262
|
<img src="https://raw.githubusercontent.com/rhosocial/python-activerecord/main/docs/images/logo.svg" alt="rhosocial ActiveRecord Logo" width="200"/>
|
|
263
|
-
<
|
|
263
|
+
<h3>PostgreSQL Backend for rhosocial-activerecord</h3>
|
|
264
|
+
<p><b>Native Array & JSONB Support · Advanced Types · Sync & Async</b></p>
|
|
264
265
|
</div>
|
|
265
266
|
|
|
266
|
-
|
|
267
|
+
> **Note**: This is a backend implementation for [rhosocial-activerecord](https://github.com/rhosocial/python-activerecord). It cannot be used standalone.
|
|
267
268
|
|
|
268
|
-
##
|
|
269
|
+
## Why This Backend?
|
|
269
270
|
|
|
270
|
-
|
|
271
|
+
### 1. PostgreSQL's Unique Type System
|
|
271
272
|
|
|
272
|
-
|
|
273
|
+
| Feature | PostgreSQL Backend | MySQL Backend | SQLite Backend |
|
|
274
|
+
|---------|-------------------|---------------|----------------|
|
|
275
|
+
| **Native Arrays** | ✅ `INTEGER[]`, `TEXT[]` | ❌ Serialize to string | ❌ Serialize to string |
|
|
276
|
+
| **JSONB** | ✅ Binary JSON, indexed | ✅ JSON (text-based) | ⚠️ JSON1 extension |
|
|
277
|
+
| **UUID** | ✅ Native type | ⚠️ CHAR(36) | ⚠️ TEXT |
|
|
278
|
+
| **Range Types** | ✅ `DATERANGE`, `INT4RANGE` | ❌ | ❌ |
|
|
279
|
+
| **RETURNING** | ✅ All operations | ❌ | ✅ |
|
|
273
280
|
|
|
274
|
-
|
|
275
|
-
- ✅ **Shared Logic**: Common functionality in `PostgresBackendMixin`
|
|
276
|
-
- ✅ **Full Transaction Support**: Including savepoints and DEFERRABLE mode
|
|
277
|
-
- ✅ **Rich Type Support**: Arrays, JSONB, UUID, ranges, network types, geometry
|
|
278
|
-
- ✅ **Complete Capability Declaration**: CTEs, window functions, JSON operations, etc.
|
|
279
|
-
- ✅ **Native Driver**: Uses psycopg3 directly, no ORM dependencies
|
|
281
|
+
### 2. No Adapter Overhead for Arrays
|
|
280
282
|
|
|
281
|
-
|
|
283
|
+
Unlike databases without native arrays, PostgreSQL stores and queries arrays directly:
|
|
282
284
|
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
```python
|
|
286
|
+
# PostgreSQL: Native array - no serialization needed
|
|
287
|
+
class Article(ActiveRecord):
|
|
288
|
+
tags: list[str] # Maps directly to TEXT[]
|
|
286
289
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
# MySQL/SQLite: Requires adapter
|
|
291
|
+
class Article(ActiveRecord):
|
|
292
|
+
tags: Annotated[list[str], UseAdapter(ListToStringAdapter(), str)]
|
|
293
|
+
```
|
|
290
294
|
|
|
291
|
-
|
|
295
|
+
### 3. Powerful Array Operators
|
|
292
296
|
|
|
293
|
-
|
|
297
|
+
Query arrays with native PostgreSQL operators:
|
|
294
298
|
|
|
295
299
|
```python
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
PostgresBackend,
|
|
299
|
-
PostgresConnectionConfig
|
|
300
|
-
)
|
|
300
|
+
# Contains
|
|
301
|
+
Article.query().where("tags @> ARRAY[?]", ('python',)).all()
|
|
301
302
|
|
|
302
|
-
#
|
|
303
|
-
|
|
304
|
-
host="localhost",
|
|
305
|
-
port=5432,
|
|
306
|
-
database="mydb",
|
|
307
|
-
username="user",
|
|
308
|
-
password="password",
|
|
309
|
-
options={
|
|
310
|
-
"sslmode": "prefer",
|
|
311
|
-
"connect_timeout": 10,
|
|
312
|
-
"application_name": "my_app"
|
|
313
|
-
}
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
# Create backend
|
|
317
|
-
backend = PostgresBackend(connection_config=config)
|
|
303
|
+
# Overlaps (any element matches)
|
|
304
|
+
Article.query().where("tags && ARRAY[?, ?]", ('python', 'database')).all()
|
|
318
305
|
|
|
319
|
-
#
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
name: str
|
|
323
|
-
email: str
|
|
306
|
+
# Any element equals
|
|
307
|
+
Article.query().where("? = ANY(tags)", ('python',)).all()
|
|
308
|
+
```
|
|
324
309
|
|
|
325
|
-
|
|
310
|
+
> 💡 **AI Prompt**: "Show me all PostgreSQL array operators with examples"
|
|
326
311
|
|
|
327
|
-
|
|
328
|
-
user = User(name="John", email="john@example.com")
|
|
329
|
-
user.save()
|
|
312
|
+
## Quick Start
|
|
330
313
|
|
|
331
|
-
|
|
332
|
-
results = User.query().with_cte(
|
|
333
|
-
"active_users",
|
|
334
|
-
User.query().where(is_active=True)
|
|
335
|
-
).from_cte("active_users").all()
|
|
314
|
+
### Installation
|
|
336
315
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
"metadata->>'role' = ?", ("admin",)
|
|
340
|
-
).all()
|
|
316
|
+
```bash
|
|
317
|
+
pip install rhosocial-activerecord-postgres
|
|
341
318
|
```
|
|
342
319
|
|
|
343
|
-
###
|
|
320
|
+
### Basic Usage
|
|
344
321
|
|
|
345
322
|
```python
|
|
346
|
-
import
|
|
347
|
-
from rhosocial.activerecord.
|
|
348
|
-
from rhosocial.activerecord.backend.impl.postgres import
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
)
|
|
323
|
+
from rhosocial.activerecord.model import ActiveRecord
|
|
324
|
+
from rhosocial.activerecord.backend.impl.postgres import PostgresBackend
|
|
325
|
+
from rhosocial.activerecord.backend.impl.postgres.config import PostgresConnectionConfig
|
|
326
|
+
from typing import Optional
|
|
327
|
+
from uuid import UUID
|
|
352
328
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
username="user",
|
|
360
|
-
password="password",
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
# Create async backend
|
|
364
|
-
backend = ActiveRecord(connection_config=config)
|
|
365
|
-
|
|
366
|
-
# Connect explicitly for async
|
|
367
|
-
await backend.connect()
|
|
368
|
-
|
|
369
|
-
# Configure model
|
|
370
|
-
class User(ActiveRecord):
|
|
371
|
-
__table_name__ = "users"
|
|
372
|
-
name: str
|
|
373
|
-
email: str
|
|
374
|
-
|
|
375
|
-
User.configure(backend)
|
|
376
|
-
|
|
377
|
-
# Use the model asynchronously
|
|
378
|
-
user = User(name="Jane", email="jane@example.com")
|
|
379
|
-
await user.save()
|
|
380
|
-
|
|
381
|
-
# Async queries
|
|
382
|
-
users = await User.query().where(is_active=True).all()
|
|
383
|
-
|
|
384
|
-
# Cleanup
|
|
385
|
-
await backend.disconnect()
|
|
386
|
-
|
|
387
|
-
# Run async code
|
|
388
|
-
asyncio.run(main())
|
|
389
|
-
```
|
|
329
|
+
class User(ActiveRecord):
|
|
330
|
+
__table_name__ = "users"
|
|
331
|
+
id: Optional[UUID] = None # Native UUID type
|
|
332
|
+
name: str
|
|
333
|
+
tags: list[str] # Native array type
|
|
334
|
+
metadata: dict # JSONB type
|
|
390
335
|
|
|
391
|
-
|
|
336
|
+
# Configure
|
|
337
|
+
config = PostgresConnectionConfig(
|
|
338
|
+
host="localhost",
|
|
339
|
+
port=5432,
|
|
340
|
+
database="myapp",
|
|
341
|
+
username="user",
|
|
342
|
+
password="password"
|
|
343
|
+
)
|
|
344
|
+
User.configure(config, PostgresBackend)
|
|
392
345
|
|
|
393
|
-
|
|
346
|
+
# Use
|
|
347
|
+
user = User(name="Alice", tags=["python", "postgres"])
|
|
348
|
+
user.save()
|
|
394
349
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
tm = backend.transaction_manager
|
|
398
|
-
|
|
399
|
-
# Basic transaction
|
|
400
|
-
with tm:
|
|
401
|
-
user1 = User(name="Alice")
|
|
402
|
-
user1.save()
|
|
403
|
-
user2 = User(name="Bob")
|
|
404
|
-
user2.save()
|
|
405
|
-
# Auto-commit on context exit
|
|
406
|
-
|
|
407
|
-
# Explicit control
|
|
408
|
-
tm.begin()
|
|
409
|
-
try:
|
|
410
|
-
user = User(name="Charlie")
|
|
411
|
-
user.save()
|
|
412
|
-
tm.commit()
|
|
413
|
-
except Exception:
|
|
414
|
-
tm.rollback()
|
|
415
|
-
raise
|
|
416
|
-
|
|
417
|
-
# With savepoints
|
|
418
|
-
tm.begin()
|
|
419
|
-
user1 = User(name="Dave")
|
|
420
|
-
user1.save()
|
|
421
|
-
|
|
422
|
-
savepoint = tm.savepoint()
|
|
423
|
-
try:
|
|
424
|
-
user2 = User(name="Eve")
|
|
425
|
-
user2.save()
|
|
426
|
-
tm.release_savepoint(savepoint)
|
|
427
|
-
except Exception:
|
|
428
|
-
tm.rollback_savepoint(savepoint)
|
|
429
|
-
|
|
430
|
-
tm.commit()
|
|
431
|
-
|
|
432
|
-
# With isolation level and deferrable mode
|
|
433
|
-
tm.set_isolation_level(IsolationLevel.SERIALIZABLE)
|
|
434
|
-
tm.set_deferrable(True)
|
|
435
|
-
with tm:
|
|
436
|
-
# Deferrable serializable transaction
|
|
437
|
-
pass
|
|
350
|
+
# Query with array operators
|
|
351
|
+
python_users = User.query().where("tags @> ARRAY[?]", ('python',)).all()
|
|
438
352
|
```
|
|
439
353
|
|
|
440
|
-
**
|
|
354
|
+
> 💡 **AI Prompt**: "How do I configure connection pooling for PostgreSQL?"
|
|
441
355
|
|
|
442
|
-
|
|
443
|
-
async def async_transaction_example():
|
|
444
|
-
# Get async transaction manager
|
|
445
|
-
tm = backend.transaction_manager
|
|
446
|
-
|
|
447
|
-
# Basic async transaction
|
|
448
|
-
async with tm:
|
|
449
|
-
user1 = User(name="Alice")
|
|
450
|
-
await user1.save()
|
|
451
|
-
user2 = User(name="Bob")
|
|
452
|
-
await user2.save()
|
|
453
|
-
# Auto-commit on context exit
|
|
454
|
-
|
|
455
|
-
# Explicit control
|
|
456
|
-
await tm.begin()
|
|
457
|
-
try:
|
|
458
|
-
user = User(name="Charlie")
|
|
459
|
-
await user.save()
|
|
460
|
-
await tm.commit()
|
|
461
|
-
except Exception:
|
|
462
|
-
await tm.rollback()
|
|
463
|
-
raise
|
|
464
|
-
|
|
465
|
-
# With savepoints
|
|
466
|
-
await tm.begin()
|
|
467
|
-
user1 = User(name="Dave")
|
|
468
|
-
await user1.save()
|
|
469
|
-
|
|
470
|
-
savepoint = await tm.savepoint()
|
|
471
|
-
try:
|
|
472
|
-
user2 = User(name="Eve")
|
|
473
|
-
await user2.save()
|
|
474
|
-
await tm.release_savepoint(savepoint)
|
|
475
|
-
except Exception:
|
|
476
|
-
await tm.rollback_savepoint(savepoint)
|
|
477
|
-
|
|
478
|
-
await tm.commit()
|
|
479
|
-
```
|
|
356
|
+
## PostgreSQL-Specific Features
|
|
480
357
|
|
|
481
|
-
###
|
|
358
|
+
### Array Types
|
|
482
359
|
|
|
483
|
-
|
|
360
|
+
Native array support without adapters:
|
|
484
361
|
|
|
485
362
|
```python
|
|
486
|
-
from rhosocial.activerecord.model import ActiveRecord
|
|
487
|
-
|
|
488
363
|
class Article(ActiveRecord):
|
|
489
364
|
__table_name__ = "articles"
|
|
490
365
|
title: str
|
|
491
|
-
tags: list
|
|
492
|
-
|
|
493
|
-
article = Article(
|
|
494
|
-
title="postgres Arrays",
|
|
495
|
-
tags=["database", "postgres", "arrays"]
|
|
496
|
-
)
|
|
497
|
-
article.save()
|
|
366
|
+
tags: list[str] # TEXT[]
|
|
367
|
+
scores: list[int] # INTEGER[]
|
|
498
368
|
|
|
499
|
-
# Query
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
).all()
|
|
369
|
+
# Query with array operators
|
|
370
|
+
Article.query().where("tags @> ARRAY[?]", ('python',)).all()
|
|
371
|
+
Article.query().where("? = ANY(tags)", ('database',)).all()
|
|
372
|
+
Article.query().where("array_length(tags, 1) > ?", (3,)).all()
|
|
503
373
|
```
|
|
504
374
|
|
|
505
|
-
|
|
375
|
+
> See [Array Type Comparison](docs/en_US/type_adapters/array_comparison.md) for differences with other databases.
|
|
506
376
|
|
|
507
|
-
|
|
508
|
-
from rhosocial.activerecord.model import ActiveRecord
|
|
377
|
+
### JSONB Operations
|
|
509
378
|
|
|
379
|
+
Binary JSON with indexing support:
|
|
380
|
+
|
|
381
|
+
```python
|
|
510
382
|
class Product(ActiveRecord):
|
|
511
383
|
__table_name__ = "products"
|
|
512
384
|
name: str
|
|
513
|
-
attributes: dict #
|
|
514
|
-
|
|
515
|
-
product = Product(
|
|
516
|
-
name="Laptop",
|
|
517
|
-
attributes={
|
|
518
|
-
"brand": "Dell",
|
|
519
|
-
"specs": {
|
|
520
|
-
"cpu": "Intel i7",
|
|
521
|
-
"ram": "16GB"
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
)
|
|
525
|
-
product.save()
|
|
385
|
+
attributes: dict # JSONB
|
|
526
386
|
|
|
527
387
|
# Query JSONB
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
# JSONB contains
|
|
533
|
-
products = Product.query().where(
|
|
534
|
-
"attributes @> ?", ('{"brand": "Dell"}',)
|
|
535
|
-
).all()
|
|
388
|
+
Product.query().where("attributes->>'brand' = ?", ("Dell",)).all()
|
|
389
|
+
|
|
390
|
+
# JSONB contains (@>)
|
|
391
|
+
Product.query().where("attributes @> ?", ('{"brand": "Dell"}',)).all()
|
|
536
392
|
```
|
|
537
393
|
|
|
538
|
-
|
|
394
|
+
### UUID Type
|
|
395
|
+
|
|
396
|
+
Native UUID storage and querying:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from uuid import UUID, uuid4
|
|
400
|
+
|
|
401
|
+
class User(ActiveRecord):
|
|
402
|
+
__table_name__ = "users"
|
|
403
|
+
id: UUID
|
|
404
|
+
name: str
|
|
405
|
+
|
|
406
|
+
user = User(id=uuid4(), name="Alice")
|
|
407
|
+
user.save()
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Range Types
|
|
411
|
+
|
|
412
|
+
Built-in support for range types:
|
|
539
413
|
|
|
540
414
|
```python
|
|
541
415
|
from datetime import date
|
|
542
|
-
from rhosocial.activerecord.model import ActiveRecord
|
|
543
416
|
|
|
544
417
|
class Booking(ActiveRecord):
|
|
545
418
|
__table_name__ = "bookings"
|
|
546
419
|
room_id: int
|
|
547
|
-
date_range: tuple #
|
|
548
|
-
|
|
549
|
-
booking = Booking(
|
|
550
|
-
room_id=101,
|
|
551
|
-
date_range=(date(2024, 1, 1), date(2024, 1, 7))
|
|
552
|
-
)
|
|
553
|
-
booking.save()
|
|
420
|
+
date_range: tuple # DATERANGE
|
|
554
421
|
|
|
555
|
-
# Query
|
|
556
|
-
|
|
557
|
-
"date_range @> ?", (date(2024, 1, 3),)
|
|
558
|
-
).all()
|
|
422
|
+
# Query with range operators
|
|
423
|
+
Booking.query().where("date_range @> ?", (date(2024, 1, 15),)).all()
|
|
559
424
|
```
|
|
560
425
|
|
|
561
|
-
|
|
426
|
+
### RETURNING Clause
|
|
562
427
|
|
|
563
|
-
|
|
428
|
+
Retrieve data after INSERT/UPDATE/DELETE:
|
|
564
429
|
|
|
565
430
|
```python
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
options={
|
|
573
|
-
# SSL/TLS
|
|
574
|
-
"sslmode": "prefer", # disable, allow, prefer, require, verify-ca, verify-full
|
|
575
|
-
|
|
576
|
-
# Connection timeout
|
|
577
|
-
"connect_timeout": 10,
|
|
578
|
-
|
|
579
|
-
# Application identification
|
|
580
|
-
"application_name": "my_app",
|
|
581
|
-
|
|
582
|
-
# Client encoding
|
|
583
|
-
"client_encoding": "UTF8",
|
|
584
|
-
|
|
585
|
-
# Connection pooling (if supported)
|
|
586
|
-
"pool_min_size": 1,
|
|
587
|
-
"pool_max_size": 10,
|
|
588
|
-
"pool_timeout": 30.0,
|
|
589
|
-
}
|
|
590
|
-
)
|
|
431
|
+
# INSERT with RETURNING
|
|
432
|
+
user = User(name="Alice")
|
|
433
|
+
user.save()
|
|
434
|
+
print(user.id) # Populated automatically via RETURNING
|
|
435
|
+
|
|
436
|
+
# Works for all operations (unlike MySQL)
|
|
591
437
|
```
|
|
592
438
|
|
|
593
|
-
##
|
|
439
|
+
## Requirements
|
|
440
|
+
|
|
441
|
+
- **Python**: 3.8+ (including 3.13t/3.14t free-threaded builds)
|
|
442
|
+
- **Core**: `rhosocial-activerecord>=1.0.0`
|
|
443
|
+
- **Driver**: `psycopg>=3.2.12`
|
|
594
444
|
|
|
595
|
-
|
|
596
|
-
|
|
445
|
+
## PostgreSQL Version Compatibility
|
|
446
|
+
|
|
447
|
+
| Feature | Min Version | Notes |
|
|
448
|
+
|---------|-------------|-------|
|
|
597
449
|
| Basic operations | 8.0+ | Core functionality |
|
|
450
|
+
| RETURNING | 8.2+ | INSERT/UPDATE/DELETE RETURNING |
|
|
598
451
|
| CTEs | 8.4+ | WITH clauses |
|
|
599
452
|
| Window functions | 8.4+ | ROW_NUMBER, RANK, etc. |
|
|
600
|
-
|
|
|
601
|
-
| JSON
|
|
602
|
-
|
|
|
603
|
-
|
|
|
453
|
+
| TRUNCATE RESTART IDENTITY | 8.4+ | Reset sequences on truncate |
|
|
454
|
+
| JSON | 9.2+ | Basic JSON support |
|
|
455
|
+
| LATERAL joins | 9.3+ | LATERAL keyword |
|
|
456
|
+
| JSONB | 9.4+ | Binary JSON, indexed |
|
|
457
|
+
| FILTER clause | 9.4+ | Aggregate FILTER |
|
|
458
|
+
| Ordered-set aggregates | 9.4+ | PERCENTILE_CONT, etc. |
|
|
459
|
+
| UPSERT | 9.5+ | INSERT ... ON CONFLICT |
|
|
460
|
+
| Advanced grouping | 9.5+ | ROLLUP, CUBE, GROUPING SETS |
|
|
461
|
+
| SKIP LOCKED | 9.5+ | FOR UPDATE SKIP LOCKED |
|
|
462
|
+
| MATERIALIZED CTE hints | 12.0+ | MATERIALIZED/NOT MATERIALIZED |
|
|
463
|
+
| JSON_TABLE | 12.0+ | JSON table function |
|
|
464
|
+
| MERGE | 15.0+ | MERGE statement |
|
|
604
465
|
|
|
605
|
-
**
|
|
466
|
+
**Supported SQL Protocols:**
|
|
467
|
+
- ✅ SetOperationSupport (UNION, INTERSECT, EXCEPT) — All versions
|
|
468
|
+
- ✅ TruncateSupport (TRUNCATE TABLE) — All versions; RESTART IDENTITY ≥ 8.4
|
|
606
469
|
|
|
607
|
-
|
|
470
|
+
**Recommended**: PostgreSQL 12+ for optimal feature support.
|
|
608
471
|
|
|
609
|
-
|
|
472
|
+
See [PROTOCOL_SUPPORT.md](docs/PROTOCOL_SUPPORT.md) for complete protocol support matrix.
|
|
610
473
|
|
|
611
|
-
|
|
612
|
-
PostgresBackendMixin (Shared Logic)
|
|
613
|
-
├── Configuration validation
|
|
614
|
-
├── Version parsing
|
|
615
|
-
├── Capability initialization
|
|
616
|
-
├── Type converter registration
|
|
617
|
-
└── Error mapping
|
|
618
|
-
|
|
619
|
-
PostgresBackend (Sync) AsyncPostgresBackend (Async)
|
|
620
|
-
├── Inherits Mixin ├── Inherits Mixin
|
|
621
|
-
├── Inherits StorageBackend ├── Inherits AsyncStorageBackend
|
|
622
|
-
├── Sync connection management ├── Async connection management
|
|
623
|
-
├── Sync query execution ├── Async query execution
|
|
624
|
-
└── Sync transaction manager └── Async transaction manager
|
|
625
|
-
```
|
|
474
|
+
## Get Started with AI Code Agents
|
|
626
475
|
|
|
627
|
-
|
|
476
|
+
This project supports AI-assisted development:
|
|
628
477
|
|
|
478
|
+
```bash
|
|
479
|
+
git clone https://github.com/rhosocial/python-activerecord-postgres.git
|
|
480
|
+
cd python-activerecord-postgres
|
|
629
481
|
```
|
|
630
|
-
PostgresTransactionMixin (Shared Logic)
|
|
631
|
-
├── Isolation level mapping
|
|
632
|
-
├── Savepoint name generation
|
|
633
|
-
├── SQL statement building
|
|
634
|
-
└── Deferrable mode support
|
|
635
|
-
|
|
636
|
-
PostgresTransactionManager AsyncPostgresTransactionManager
|
|
637
|
-
├── Inherits Mixin ├── Inherits Mixin
|
|
638
|
-
├── Inherits TransactionManager ├── Inherits AsyncTransactionManager
|
|
639
|
-
├── Sync transaction operations ├── Async transaction operations
|
|
640
|
-
└── Sync constraint management └── Async constraint management
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
## Testing
|
|
644
482
|
|
|
645
|
-
|
|
483
|
+
### Example AI Prompts
|
|
646
484
|
|
|
647
|
-
-
|
|
648
|
-
-
|
|
649
|
-
-
|
|
650
|
-
-
|
|
651
|
-
- Capability declaration verification
|
|
652
|
-
- Error handling tests
|
|
653
|
-
- Concurrent operation tests
|
|
485
|
+
- "How do I use array operators to query tags?"
|
|
486
|
+
- "What's the difference between JSON and JSONB in PostgreSQL?"
|
|
487
|
+
- "Show me how to create a GIN index on an array column"
|
|
488
|
+
- "How do I use range types for scheduling?"
|
|
654
489
|
|
|
655
|
-
|
|
656
|
-
```bash
|
|
657
|
-
pytest tests/rhosocial/activerecord_test/feature/backend/postgres/test_backend_sync.py
|
|
658
|
-
```
|
|
490
|
+
### For Any LLM
|
|
659
491
|
|
|
660
|
-
|
|
661
|
-
```bash
|
|
662
|
-
pytest tests/rhosocial/activerecord_test/feature/backend/postgres/test_backend_async.py
|
|
663
|
-
```
|
|
492
|
+
Feed the documentation files in `docs/` for context-aware assistance.
|
|
664
493
|
|
|
665
|
-
##
|
|
494
|
+
## Testing
|
|
666
495
|
|
|
667
|
-
|
|
496
|
+
> ⚠️ **CRITICAL**: Tests MUST run serially. Do NOT use `pytest -n auto` or parallel execution.
|
|
668
497
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
498
|
+
```bash
|
|
499
|
+
# Run all tests
|
|
500
|
+
PYTHONPATH=src pytest tests/
|
|
672
501
|
|
|
673
|
-
|
|
502
|
+
# Run specific feature tests
|
|
503
|
+
PYTHONPATH=src pytest tests/rhosocial/activerecord_postgres_test/feature/basic/
|
|
504
|
+
PYTHONPATH=src pytest tests/rhosocial/activerecord_postgres_test/feature/query/
|
|
674
505
|
```
|
|
675
506
|
|
|
676
|
-
|
|
677
|
-
```python
|
|
678
|
-
from rhosocial.activerecord.backend.impl.postgres import PostgresBackend
|
|
507
|
+
See the [Testing Documentation](https://github.com/rhosocial/python-activerecord/blob/main/.claude/testing.md) for details.
|
|
679
508
|
|
|
680
|
-
|
|
681
|
-
```
|
|
509
|
+
## Documentation
|
|
682
510
|
|
|
683
|
-
**
|
|
684
|
-
|
|
685
|
-
|
|
511
|
+
- **[Getting Started](docs/en_US/getting_started/)** — Installation and configuration
|
|
512
|
+
- **[PostgreSQL Features](docs/en_US/postgres_specific_features/)** — PostgreSQL-specific capabilities
|
|
513
|
+
- **[Type Adapters](docs/en_US/type_adapters/)** — Data type handling
|
|
514
|
+
- **[Array Comparison](docs/en_US/type_adapters/array_comparison.md)** — Array support across databases
|
|
515
|
+
- **[Transaction Support](docs/en_US/transaction_support/)** — Transaction management
|
|
686
516
|
|
|
687
|
-
|
|
688
|
-
await backend.connect()
|
|
689
|
-
```
|
|
517
|
+
## Comparison with Other Backends
|
|
690
518
|
|
|
691
|
-
|
|
519
|
+
| Feature | PostgreSQL | MySQL | SQLite |
|
|
520
|
+
|---------|------------|-------|--------|
|
|
521
|
+
| **Native Arrays** | ✅ | ❌ | ❌ |
|
|
522
|
+
| **JSONB (indexed)** | ✅ | ⚠️ JSON only | ⚠️ Extension |
|
|
523
|
+
| **UUID Type** | ✅ Native | ⚠️ CHAR(36) | ⚠️ TEXT |
|
|
524
|
+
| **Range Types** | ✅ | ❌ | ❌ |
|
|
525
|
+
| **RETURNING** | ✅ | ❌ | ✅ |
|
|
526
|
+
| **CTEs** | ✅ 8.4+ | ✅ 8.0+ | ✅ 3.8.3+ |
|
|
527
|
+
| **Full-Text Search** | ✅ | ✅ | ⚠️ FTS5 |
|
|
692
528
|
|
|
693
|
-
|
|
694
|
-
2. **Async Context**: Async backend requires explicit `await backend.connect()` call
|
|
695
|
-
3. **Type Converters**: Some postgres-specific types may need custom converters
|
|
529
|
+
> 💡 **AI Prompt**: "When should I choose PostgreSQL over MySQL for my project?"
|
|
696
530
|
|
|
697
531
|
## Contributing
|
|
698
532
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
- Tests pass for both sync and async implementations
|
|
702
|
-
- Code follows project style guidelines
|
|
703
|
-
- Documentation is updated
|
|
704
|
-
- Changelog fragments are created
|
|
533
|
+
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
705
534
|
|
|
706
535
|
## License
|
|
707
536
|
|
|
708
|
-
[
|
|
537
|
+
[Apache License 2.0](LICENSE) — Copyright © 2026 [vistart](https://github.com/vistart)
|
|
709
538
|
|
|
710
|
-
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
<div align="center">
|
|
542
|
+
<p><b>Built with ❤️ by the rhosocial team</b></p>
|
|
543
|
+
<p><a href="https://github.com/rhosocial/python-activerecord-postgres">GitHub</a> · <a href="https://docs.python-activerecord.dev.rho.social/backends/postgres.html">Documentation</a> · <a href="https://pypi.org/project/rhosocial-activerecord-postgres/">PyPI</a></p>
|
|
544
|
+
</div>
|