surreal-orm-lite 0.4.0__tar.gz → 0.6.0__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 (23) hide show
  1. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/CHANGELOG.md +83 -0
  2. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/PKG-INFO +178 -47
  3. surreal_orm_lite-0.6.0/README.md +439 -0
  4. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/pyproject.toml +2 -2
  5. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/__init__.py +3 -1
  6. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/constants.py +4 -4
  7. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/model_base.py +219 -0
  8. surreal_orm_lite-0.6.0/src/surreal_orm_lite/q.py +112 -0
  9. surreal_orm_lite-0.6.0/src/surreal_orm_lite/query_set.py +714 -0
  10. surreal_orm_lite-0.6.0/src/surreal_orm_lite/utils.py +186 -0
  11. surreal_orm_lite-0.4.0/README.md +0 -308
  12. surreal_orm_lite-0.4.0/src/surreal_orm_lite/query_set.py +0 -870
  13. surreal_orm_lite-0.4.0/src/surreal_orm_lite/utils.py +0 -53
  14. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/.gitignore +0 -0
  15. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/LICENSE +0 -0
  16. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/Makefile +0 -0
  17. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/__init__.py +0 -0
  18. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/aggregations.py +0 -0
  19. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/connection_manager.py +0 -0
  20. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/enum.py +0 -0
  21. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/exceptions.py +0 -0
  22. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/py.typed +0 -0
  23. {surreal_orm_lite-0.4.0 → surreal_orm_lite-0.6.0}/src/surreal_orm_lite/signals.py +0 -0
@@ -5,6 +5,89 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2026-03-10
9
+
10
+ ### Added
11
+
12
+ - **Relations & Graph**: Full SurrealDB graph relation support on `BaseSurrealModel`
13
+ - `relate(edge, target, data=)` — Create graph relations (`RELATE source->edge->target`)
14
+ - `remove_relation(edge, target)` — Remove a specific relation
15
+ - `remove_all_relations(edge, direction=)` — Remove all relations of a type (`out`, `in`, `both`)
16
+ - `get_related(edge, direction=, model_class=)` — Retrieve related records through an edge
17
+ - `traverse(path)` — Graph traversal via SurrealQL path syntax (e.g. `->follows->User->follows->User`)
18
+
19
+ - **FETCH Clause**: `QuerySet.fetch(*fields)` resolves record links inline, preventing N+1 queries
20
+ - `Post.objects().fetch("author", "tags").exec()` generates `SELECT * FROM Post FETCH author, tags;`
21
+
22
+ - **Validation Utilities**: New security validators in `utils.py`
23
+ - `validate_edge_name()` — Validates edge/relation table names
24
+ - `validate_graph_path()` — Validates graph traversal paths (strict arrow-segment structure)
25
+ - `validate_thing()` — Validates `table:id` record identifiers against injection
26
+
27
+ - New test file `tests/test_relations.py` with unit + E2E tests
28
+
29
+ - **CI/CD Workflows**:
30
+ - `.surrealdb-version` file to pin the tested SurrealDB version (2.6.0)
31
+ - `surrealdb-security.yml` — Daily SurrealDB 2.X version monitor (test, auto-PR, auto-issue)
32
+ - `dependabot-automerge.yml` — Auto-merge Dependabot PRs with test validation and version bump
33
+
34
+ ### Changed
35
+
36
+ - `QuerySet._compile_query()` now appends `FETCH` clause when `fetch()` is used
37
+ - `QuerySet.fetch()` now accumulates fields across chained calls instead of overwriting
38
+ - `QuerySet.variables()` now merges variables across chained calls instead of overwriting
39
+ - Coverage: 92.80%
40
+
41
+ ### Fixed
42
+
43
+ - **Security**: `_resolve_target_thing()` now validates string targets with `validate_thing()` to prevent SurrealQL injection
44
+ - **Security**: `_get_thing()` now validates the generated `table:id` string to prevent injection via malicious model IDs
45
+ - **Security**: `get_related()` now validates RecordIDs with `validate_thing()` before interpolation
46
+ - **Security**: `$` variable references in filters are now validated against `^\$[a-zA-Z_][a-zA-Z0-9_]*$` pattern
47
+ - **Security**: `validate_graph_path()` regex tightened to require arrow-separated segments (`->` or `<-`) — rejects arbitrary `<>-` combinations
48
+ - Removed misleading case-insensitive lookup aliases (`icontains`, `istartswith`, `iendswith`, `iregex`) that were mapped to case-sensitive SurrealDB operators
49
+
50
+ ## [0.5.0] - 2026-02-11
51
+
52
+ ### Added
53
+
54
+ - **Q Objects**: Django-style composable query expressions for complex filters
55
+ - `Q(field=value)` for basic conditions
56
+ - `Q(...) | Q(...)` for OR queries
57
+ - `Q(...) & Q(...)` for AND queries
58
+ - `~Q(...)` for NOT queries
59
+ - Nested combinations: `Q(age__gte=18) & (Q(role="admin") | Q(role="mod"))`
60
+
61
+ - **Parameterized Filters**: All filter values now use parameterized variables (`$_fN`) instead of string interpolation, preventing SQL injection
62
+
63
+ - **New Lookup Operators**:
64
+ - `not_in` - NOT IN operator
65
+ - `not_contains` - CONTAINSNOT operator
66
+ - `containsall` - CONTAINSALL operator
67
+ - `containsany` - CONTAINSANY operator
68
+
69
+ - **`-field` Ordering Shorthand**: Prefix field with `-` for descending order
70
+ - `order_by("-created_at")` instead of `order_by("created_at", OrderBy.DESC)`
71
+ - Multi-field ordering: `order_by("-age", "name")`
72
+
73
+ - **Bulk Operations**:
74
+ - `bulk_create(models)` - Create multiple records via SDK's `insert()`
75
+ - `bulk_update(**kwargs)` - Update all matching records with parameterized SET clause
76
+ - `bulk_delete()` - Delete all matching records, returns count
77
+
78
+ - New `Q` class exported from `surreal_orm_lite`
79
+ - New test file `tests/test_v050.py` for all v0.5.0 features
80
+
81
+ ### Changed
82
+
83
+ - `_compile_query()`, `_compile_aggregation_query()`, `_compile_group_by_query()` now return `tuple[str, dict]` with parameterized variables
84
+ - `_execute_query()` now accepts optional `variables` parameter
85
+ - `filter()` now accepts `*args: Q` positional arguments alongside keyword filters
86
+ - `order_by()` now accepts multiple fields with `-field` prefix support
87
+ - `_build_where()` replaces `_build_where_clauses()` and returns parameterized WHERE clause
88
+ - Shared `parse_lookup()` and `build_filter_condition()` functions moved to `utils.py`
89
+ - Fixed `isnull` lookup: now correctly generates `IS NULL`/`IS NOT NULL`
90
+
8
91
  ## [0.4.0] - 2026-02-07
9
92
 
10
93
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: surreal-orm-lite
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Lightweight Django-style ORM for SurrealDB using the official Python SDK. Async support with Pydantic validation.
5
5
  Project-URL: Homepage, https://github.com/EulogySnowfall/SurrealDB-ORM-lite
6
6
  Project-URL: Documentation, https://github.com/EulogySnowfall/SurrealDB-ORM-lite
@@ -31,7 +31,7 @@ License: # MIT License
31
31
  SOFTWARE.
32
32
  License-File: LICENSE
33
33
  Keywords: async,database,orm,pydantic,surrealdb
34
- Classifier: Development Status :: 4 - Beta
34
+ Classifier: Development Status :: 3 - Alpha
35
35
  Classifier: Framework :: Pydantic :: 2
36
36
  Classifier: License :: OSI Approved :: MIT License
37
37
  Classifier: Operating System :: OS Independent
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
51
51
  # Surreal ORM Lite
52
52
 
53
53
  ![Python](https://img.shields.io/badge/python-3.11%2B-blue)
54
- ![SurrealDB](https://img.shields.io/badge/SurrealDB-2.6.0-purple)
54
+ ![SurrealDB](https://img.shields.io/badge/SurrealDB-2.6.3-purple)
55
55
  ![SDK](https://img.shields.io/badge/SDK-Official%201.0.8-green)
56
56
  ![License](https://img.shields.io/badge/license-MIT-blue)
57
57
  [![codecov](https://codecov.io/gh/EulogySnowfall/SurrealDB-ORM-lite/graph/badge.svg)](https://codecov.io/gh/EulogySnowfall/SurrealDB-ORM-lite)
@@ -74,10 +74,12 @@ This ORM is designed to:
74
74
  | Dependency | Version |
75
75
  | ------------ | ---------------- |
76
76
  | Python | 3.11+ |
77
- | SurrealDB | 2.6.0+ |
77
+ | SurrealDB | >=2.6.x, <3.0 |
78
78
  | Official SDK | surrealdb>=1.0.8 |
79
79
  | Pydantic | >=2.12.5 |
80
80
 
81
+ > **Note**: The official SurrealDB Python SDK (`surrealdb>=1.0.8`) only supports SurrealDB 2.X. For SurrealDB 3.X support, use the full [SurrealDB-ORM](https://github.com/EulogySnowfall/SurrealDB-ORM/) (v0.30.0+).
82
+
81
83
  ---
82
84
 
83
85
  ## Installation
@@ -154,9 +156,9 @@ users = await User.objects().filter(
154
156
  name__startswith="A"
155
157
  ).exec()
156
158
 
157
- # Ordering
159
+ # Ordering (with -field shorthand for DESC)
158
160
  users = await User.objects().order_by("name").exec()
159
- users = await User.objects().order_by("age", OrderBy.DESC).exec()
161
+ users = await User.objects().order_by("-age", "name").exec()
160
162
 
161
163
  # Pagination
162
164
  users = await User.objects().limit(10).offset(20).exec()
@@ -181,30 +183,110 @@ results = await User.objects().query(
181
183
 
182
184
  ## Features
183
185
 
184
- | Feature | Status |
185
- | --------------------- | ------ |
186
- | Async/await support | ✅ |
187
- | Pydantic validation | ✅ |
188
- | CRUD operations | ✅ |
189
- | QuerySet with filters | ✅ |
190
- | Django-style lookups | ✅ |
191
- | Custom primary keys | ✅ |
192
- | HTTP connections | ✅ |
193
- | WebSocket connections | ✅ |
194
- | Aggregations | ✅ |
195
- | GROUP BY | ✅ |
196
- | Model Signals | ✅ |
186
+ | Feature | Status |
187
+ | ---------------------- | ------ |
188
+ | Async/await support | ✅ |
189
+ | Pydantic validation | ✅ |
190
+ | CRUD operations | ✅ |
191
+ | QuerySet with filters | ✅ |
192
+ | Django-style lookups | ✅ |
193
+ | Custom primary keys | ✅ |
194
+ | HTTP connections | ✅ |
195
+ | WebSocket connections | ✅ |
196
+ | Aggregations | ✅ |
197
+ | GROUP BY | ✅ |
198
+ | Model Signals | ✅ |
199
+ | Raw SurrealQL queries | ✅ |
200
+ | Q Objects (OR/AND/NOT) | ✅ |
201
+ | Parameterized filters | ✅ |
202
+ | Bulk operations | ✅ |
203
+ | `-field` ordering | ✅ |
204
+ | Relations & Graph | ✅ |
205
+ | FETCH clause | ✅ |
197
206
 
198
207
  ### Supported Filter Lookups
199
208
 
200
209
  - `exact` (default)
201
210
  - `gt`, `gte`, `lt`, `lte`
202
- - `in`
203
- - `contains`, `icontains`
204
- - `startswith`, `istartswith`
205
- - `endswith`, `iendswith`
211
+ - `in`, `not_in`
212
+ - `contains`, `not_contains`
213
+ - `containsall`, `containsany`
214
+ - `startswith`, `endswith`
215
+ - `like`, `ilike`
216
+ - `match`, `regex`
217
+ - `isnull`
218
+
219
+ ### 5. Q Objects (Complex Queries)
220
+
221
+ ```python
222
+ from surreal_orm_lite import Q
223
+
224
+ # OR queries
225
+ users = await User.objects().filter(Q(name="Alice") | Q(name="Bob")).exec()
226
+
227
+ # NOT queries
228
+ active = await User.objects().filter(~Q(status="banned")).exec()
229
+
230
+ # Complex combinations
231
+ results = await User.objects().filter(
232
+ Q(age__gte=18) & (Q(role="admin") | Q(role="mod"))
233
+ ).exec()
234
+
235
+ # Mix Q objects with keyword filters
236
+ results = await User.objects().filter(
237
+ Q(role="admin") | Q(role="mod"),
238
+ age__gte=25
239
+ ).exec()
240
+ ```
241
+
242
+ ### 6. Bulk Operations
243
+
244
+ ```python
245
+ # Bulk create
246
+ users = [User(name="Alice", age=30), User(name="Bob", age=25)]
247
+ created = await User.objects().bulk_create(users)
248
+
249
+ # Bulk update (returns count of updated records)
250
+ count = await User.objects().filter(status="pending").bulk_update(status="active")
251
+
252
+ # Bulk delete (returns count of deleted records)
253
+ count = await User.objects().filter(status="inactive").bulk_delete()
254
+ ```
255
+
256
+ ### 7. Relations & Graph
257
+
258
+ ```python
259
+ # Create a relation
260
+ await user.relate("follows", other_user)
261
+
262
+ # With data on the edge
263
+ await user.relate("purchased", product, data={"quantity": 2, "price": 29.99})
264
+
265
+ # Get related records (outgoing)
266
+ following = await user.get_related("follows", direction="out", model_class=User)
267
+
268
+ # Get related records (incoming)
269
+ followers = await user.get_related("follows", direction="in", model_class=User)
270
+
271
+ # Remove a specific relation
272
+ await user.remove_relation("follows", other_user)
206
273
 
207
- ### 5. Aggregations
274
+ # Remove all outgoing relations of a type
275
+ await user.remove_all_relations("follows", direction="out")
276
+
277
+ # Graph traversal
278
+ friends_of_friends = await user.traverse("->follows->User->follows->User")
279
+ ```
280
+
281
+ ### 8. FETCH Clause
282
+
283
+ ```python
284
+ # Resolve record links inline (prevents N+1 queries)
285
+ posts = await Post.objects().fetch("author", "tags").exec()
286
+ # Generates: SELECT * FROM Post FETCH author, tags;
287
+ ```
288
+
289
+ ### 9. Aggregations
208
290
 
209
291
  ```python
210
292
  from surreal_orm_lite import Count, Sum, Avg, Min, Max
@@ -230,7 +312,7 @@ results = await User.raw_query(
230
312
  )
231
313
  ```
232
314
 
233
- ### 6. Model Signals
315
+ ### 10. Model Signals
234
316
 
235
317
  ```python
236
318
  from surreal_orm_lite import pre_save, post_save, pre_delete, post_delete
@@ -250,17 +332,17 @@ async def on_user_deleting(sender, instance, **kwargs):
250
332
 
251
333
  **Available signals:**
252
334
 
253
- | Signal | When | Extra kwargs |
254
- | --------------- | --------------------------- | ---------------- |
255
- | `pre_save` | Before `save()` | |
256
- | `post_save` | After `save()` | `created` |
257
- | `pre_update` | Before `update()`/`merge()` | `update_fields` |
258
- | `post_update` | After `update()`/`merge()` | `update_fields` |
259
- | `pre_delete` | Before `delete()` | |
260
- | `post_delete` | After `delete()` | |
261
- | `around_save` | Wraps `save()` | |
262
- | `around_update` | Wraps `update()`/`merge()` | `update_fields` |
263
- | `around_delete` | Wraps `delete()` | |
335
+ | Signal | When | Extra kwargs |
336
+ | --------------- | --------------------------- | --------------- |
337
+ | `pre_save` | Before `save()` | |
338
+ | `post_save` | After `save()` | `created` |
339
+ | `pre_update` | Before `update()`/`merge()` | `update_fields` |
340
+ | `post_update` | After `update()`/`merge()` | `update_fields` |
341
+ | `pre_delete` | Before `delete()` | |
342
+ | `post_delete` | After `delete()` | |
343
+ | `around_save` | Wraps `save()` | |
344
+ | `around_update` | Wraps `update()`/`merge()` | `update_fields` |
345
+ | `around_delete` | Wraps `delete()` | |
264
346
 
265
347
  **Around signals** use async generators to wrap operations:
266
348
 
@@ -305,12 +387,14 @@ async with SurrealDBConnectionManager():
305
387
 
306
388
  ## Compatibility
307
389
 
308
- This ORM is tested and compatible with:
390
+ This ORM is tested and compatible with SurrealDB 2.X only (SDK limitation). For SurrealDB 3.X, use [SurrealDB-ORM](https://github.com/EulogySnowfall/SurrealDB-ORM/) v0.30.0+.
309
391
 
310
- | SurrealDB Version | SDK Version | Status |
311
- | ----------------- | ----------- | ------------- |
312
- | 2.6.0 | 1.0.8 | ✅ Tested |
313
- | 2.5.x | 1.0.8 | ✅ Compatible |
392
+ | SurrealDB Version | SDK Version | Status |
393
+ | ----------------- | ----------- | ------------------ |
394
+ | 2.6.3 | 1.0.8 | ✅ Tested |
395
+ | 2.6.x | 1.0.8 | ✅ Compatible |
396
+ | 2.5.x | 1.0.8 | ✅ Compatible |
397
+ | 3.x | — | ❌ Not supported |
314
398
 
315
399
  ---
316
400
 
@@ -326,16 +410,62 @@ Contributions are welcome! Please:
326
410
 
327
411
  ---
328
412
 
329
- ## Advanced Features?
413
+ ## Roadmap
330
414
 
331
- This project prioritizes **stability and compatibility** with the official SurrealDB Python SDK. Due to current SDK limitations, some advanced features cannot be implemented here.
415
+ | Version | Theme | Status |
416
+ | ------- | ----------------------------- | ----------- |
417
+ | v0.2.x | Core ORM (CRUD, QuerySet) | ✅ Released |
418
+ | v0.3.0 | Aggregations & Utilities | ✅ Released |
419
+ | v0.4.0 | Model Signals | ✅ Released |
420
+ | v0.5.0 | Bulk Operations & Q Objects | ✅ Released |
421
+ | v0.6.0 | Relations & Graph | ✅ Released |
422
+ | v0.7.0 | Transactions ORM | 📋 Planned |
423
+ | v0.8.0 | SurrealFunc & Computed Fields | 📋 Planned |
424
+ | v0.9.0 | Field Aliases & DX | 📋 Planned |
425
+ | v1.0.0 | Production Ready | 📋 Planned |
332
426
 
333
- For a feature-rich ORM with relations, transactions, and more, see:
427
+ See [docs/ROADMAP.md](docs/ROADMAP.md) for full details.
334
428
 
335
- - **GitHub**: [SurrealDB-ORM](https://github.com/EulogySnowfall/SurrealDB-ORM/)
336
- - **PyPI**: [surrealdb-orm](https://pypi.org/project/surrealdb-orm/)
429
+ ---
337
430
 
338
- When the official SDK supports additional features, they will be incorporated into this lite version.
431
+ ## SurrealDB-ORM-lite vs SurrealDB-ORM
432
+
433
+ This project prioritizes **stability and compatibility** with the official SurrealDB Python SDK. The full [SurrealDB-ORM](https://github.com/EulogySnowfall/SurrealDB-ORM/) uses a custom SDK for advanced features.
434
+
435
+ | Feature | ORM-lite (official SDK) | ORM (custom SDK) |
436
+ | ------------------------- | ----------------------- | ---------------- |
437
+ | CRUD & QuerySet | ✅ | ✅ |
438
+ | Aggregations & GROUP BY | ✅ | ✅ |
439
+ | Model Signals | ✅ | ✅ |
440
+ | Bulk Operations | ✅ | ✅ |
441
+ | Q Objects (OR/AND/NOT) | ✅ | ✅ |
442
+ | Parameterized Filters | ✅ | ✅ |
443
+ | Relations & Graph | ✅ | ✅ |
444
+ | FETCH clause | ✅ | ✅ |
445
+ | Transactions (tx=) | v0.7.0 | ✅ |
446
+ | SurrealFunc & Computed | v0.8.0 | ✅ |
447
+ | Field Aliases | v0.9.0 | ✅ |
448
+ | Retry, Logging, Metrics | v0.10.0 | ✅ |
449
+ | Live Models / CDC | ❌ | ✅ |
450
+ | Vector / Full-Text Search | ❌ | ✅ |
451
+ | Hybrid Search (RRF) | ❌ | ✅ |
452
+ | Migrations & CLI | ❌ | ✅ |
453
+ | JWT Authentication | ❌ | ✅ |
454
+ | Schema Introspection | ❌ | ✅ |
455
+ | Connection Pool | ❌ | ✅ |
456
+ | CBOR Protocol | ❌ | ✅ |
457
+ | Subqueries & Query Cache | ❌ | ✅ |
458
+ | Geospatial Fields | ❌ | ✅ |
459
+ | DEFINE EVENT | ❌ | ✅ |
460
+ | Test Fixtures & Factories | ❌ | ✅ |
461
+ | Atomic Array Operations | ❌ | ✅ |
462
+
463
+ **Choose ORM-lite** if you want the official SDK, minimal dependencies, and core ORM features.
464
+
465
+ **Choose ORM** if you need live queries, migrations, authentication, vector search, or advanced features.
466
+
467
+ - **SurrealDB-ORM GitHub**: [github.com/EulogySnowfall/SurrealDB-ORM](https://github.com/EulogySnowfall/SurrealDB-ORM/)
468
+ - **SurrealDB-ORM PyPI**: [surrealdb-orm](https://pypi.org/project/surrealdb-orm/)
339
469
 
340
470
  ---
341
471
 
@@ -356,3 +486,4 @@ GitHub: [@EulogySnowfall](https://github.com/EulogySnowfall)
356
486
 
357
487
  - [SurrealDB](https://surrealdb.com/) - The database
358
488
  - [surrealdb.py](https://github.com/surrealdb/surrealdb.py) - Official Python SDK
489
+ - [SurrealDB-ORM](https://github.com/EulogySnowfall/SurrealDB-ORM/) - Full-featured ORM with custom SDK