surreal-orm-lite 0.31.0__tar.gz → 0.31.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 (47) hide show
  1. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/CHANGELOG.md +102 -0
  2. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/PKG-INFO +55 -24
  3. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/README.md +54 -23
  4. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/pyproject.toml +1 -1
  5. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/__init__.py +1 -1
  6. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/_sdk.py +6 -1
  7. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/concurrency.py +7 -2
  8. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/connection_manager.py +11 -5
  9. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/decoding.py +112 -16
  10. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/model_base.py +2 -2
  11. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/transaction.py +206 -43
  12. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/.gitignore +0 -0
  13. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/LICENSE +0 -0
  14. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/Makefile +0 -0
  15. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/aggregations.py +0 -0
  16. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/auth.py +0 -0
  17. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/cache.py +0 -0
  18. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/changefeed.py +0 -0
  19. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/connection_config.py +0 -0
  20. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/constants.py +0 -0
  21. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/define_parser.py +0 -0
  22. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/edge.py +0 -0
  23. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/embedded.py +0 -0
  24. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/encoding.py +0 -0
  25. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/enum.py +0 -0
  26. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/exceptions.py +0 -0
  27. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/functions.py +0 -0
  28. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/geo.py +0 -0
  29. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/introspection.py +0 -0
  30. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/live.py +0 -0
  31. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/model_auth.py +0 -0
  32. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/model_generator.py +0 -0
  33. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/py.typed +0 -0
  34. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/q.py +0 -0
  35. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/query_set.py +0 -0
  36. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/routing.py +0 -0
  37. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/schema.py +0 -0
  38. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/schema_state.py +0 -0
  39. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/signals.py +0 -0
  40. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/subquery.py +0 -0
  41. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/surql_literal.py +0 -0
  42. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/testing/__init__.py +0 -0
  43. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/testing/database.py +0 -0
  44. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/testing/fixtures.py +0 -0
  45. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/time_travel.py +0 -0
  46. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/types.py +0 -0
  47. {surreal_orm_lite-0.31.0 → surreal_orm_lite-0.31.2}/src/surreal_orm_lite/utils.py +0 -0
@@ -5,6 +5,108 @@ 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.31.2] - 2026-10-19
9
+
10
+ Transaction errors give the server's reason and the failed statement's position, without the
11
+ server's excerpt of the statement, and a buffered commit raises `SurrealDbError` on the embedded
12
+ engine too (#278). No new public API.
13
+
14
+ ### Fixed
15
+
16
+ - **Transaction errors give the server's reason and the statement's position, without the
17
+ server's excerpt of the statement** (#278). When the server refused the query as a whole (a
18
+ statement it cannot parse), the error read "Transaction failed: unrecognized query_raw()
19
+ response shape: {…}" and quoted the server's raw response, its excerpt of the statement
20
+ included. It now gives the server's reason and the position:
21
+
22
+ - at a buffered commit (SurrealDB 2.x, HTTP, the embedded engine), "Transaction failed: the
23
+ server refused statement 2 of 3: … (at line 1, column 7 of the statement)", the line and column
24
+ of the statement as written, before the ORM renames its `$name` parameters for the batch; a
25
+ delimiter left unclosed names the statement it opens in, even when the server first reports it
26
+ in a later statement or on the batch's `COMMIT` line;
27
+ - for a write or a definition in an interactive transaction (SurrealDB 3.x over WebSocket),
28
+ "Transaction failed: the server refused the statement: … (at line 1, column 7 of the
29
+ statement)";
30
+ - a failed statement's error, "Transaction failed and rolled back: …", keeps only the first line
31
+ of a reason that quotes the query;
32
+ - a response of any other shape raises "Transaction failed: unrecognized query_raw() response
33
+ shape (<type>)", without the response.
34
+
35
+ The server's full text stays reachable as the error's `__context__`, which a formatted traceback
36
+ does not print. A read in a transaction still raises the SDK's own error, as it does outside a
37
+ transaction.
38
+
39
+ - **A buffered commit on the embedded engine raises `SurrealDbError`** (#278). The engine reports a
40
+ query it cannot parse as a bare `RuntimeError`, which propagated out of the `transaction()` block;
41
+ the commit now raises `SurrealDbError`, or `SurrealDbConflictError` for a retryable conflict, as
42
+ on a server.
43
+ - **A statement holding conflict words is not taken for a conflict** (#278). When the server could
44
+ not parse a statement holding words such as "write conflict", `is_conflict_error()` recognised
45
+ the transaction's error as a retryable conflict, so `retry_on_conflict` retried it, and an
46
+ interactive transaction's cancellation error was a `SurrealDbConflictError`. Conflicts are now
47
+ recognised from the server's reason, not from its excerpt of the statement.
48
+
49
+ ### Changed
50
+
51
+ - **The cancellation error names the failed statement's error type** (#278). Since v0.30.3,
52
+ leaving an interactive `transaction()` block after a failed statement, or an explicit
53
+ `tx.commit()`, raises "The transaction was cancelled: a statement in it failed; nothing was
54
+ committed (…)", whose parenthesis repeated the failed statement's error text. The parenthesis now
55
+ holds that error's type name, for example `(SurrealDbError)`. The error itself is still chained
56
+ as `__cause__`, and a retryable conflict still raises `SurrealDbConflictError`, so
57
+ `retry_on_conflict` behaves as before.
58
+ - **`is_conflict_error()` follows the chain a traceback prints** (#278): a context suppressed with
59
+ `raise … from None` is no longer read, since the ORM keeps the server's full text there, which
60
+ may quote the statement; `__cause__` and an implicit `__context__` are still followed. A conflict
61
+ your code re-raises as another exception with `raise … from None` is therefore no longer
62
+ recognised, nor retried by `retry_on_conflict`: chain it (`raise … from exc`, or `raise …`
63
+ without `from` inside the `except` block), or raise a `SurrealDbConflictError`, to keep it
64
+ retryable.
65
+
66
+ ### Notes
67
+
68
+ - The v0.31.0 caution on the DDL appliers with `tx=` (#278), that an error raised when the
69
+ transaction ends can carry the server's excerpt of the statement, no longer applies: that error
70
+ now gives the server's reason and the statement's position instead. README sections 11 and 32
71
+ and the embedded-engine table say so.
72
+ - Same behaviour on SurrealDB 2.x, 3.x and the embedded engine. Server pins unchanged: tested
73
+ against SurrealDB 3.3.4 and 2.7.0 and on the embedded engine, plus 3.1.5 for backward
74
+ compatibility.
75
+
76
+ ## [0.31.1] - 2026-10-19
77
+
78
+ A datetime outside years 1-9999 no longer fails the response it is in (#279). SurrealDB stores
79
+ datetimes far beyond the years a Python `datetime` holds, and the ORM now reads them back as the
80
+ server's own text. No new public API.
81
+
82
+ ### Fixed
83
+
84
+ - **Reading a datetime before year 1 or after year 9999** (#279). SurrealDB stores datetimes from
85
+ year -262143 to +262142, on 2.x and 3.x, but the surrealdb SDK 2.0.0 decodes a datetime into a
86
+ Python `datetime`, which holds years 1-9999 only: one such value failed the whole response it
87
+ was in, whether it came from `get()`, `filter()`, `raw_query()` or a live notification. The
88
+ ORM's clients now read it as its RFC 3339 text, the one SurrealDB gives it with `<string>`:
89
+ `"0000-06-01T00:00:00Z"`, `"-0001-01-01T00:00:00Z"`, `"+10000-01-01T00:00:00.500Z"` (a sign and
90
+ at least four digits outside years 0000-9999, every nanosecond kept). An untyped field holds
91
+ that string; a `datetime` field refuses it with a `ValidationError` naming the field, so `get()`
92
+ raises it and `filter().exec()` and `raw_query()` return such rows as dicts, as for any row
93
+ that does not validate. Written back through the ORM, the value is stored as that string. A
94
+ datetime within years 1-9999, before 1970 included, reads as the SDK's `datetime`, as before.
95
+
96
+ ### Notes
97
+
98
+ - As for zero durations, native sets and degenerate geometries, only the clients the ORM opens
99
+ (`SurrealDBConnectionManager.get_client()`, `ephemeral_client()`, the embedded engine's
100
+ included) read these values; an `AsyncSurreal` client the application builds itself keeps the
101
+ SDK's behaviour. The ORM leaves them to the SDK as soon as an installed SDK decodes them itself.
102
+ README: "Values the SDK cannot read" and a new row of the 2.x/3.x behaviour table.
103
+ - The function catalogue test (`TestFunctionEnumsE2E`) calls `rand::time()` between 2000 and 2030
104
+ and checks the result. Unbounded, it drew a year-0 datetime in about one call in 7,400 (27 in
105
+ 200,000 on each line), which made the test fail at random.
106
+ - Same behaviour on SurrealDB 2.x, 3.x and the embedded engine. Server pins unchanged: tested
107
+ against SurrealDB 3.3.4 and 2.7.0 and on the embedded engine, plus 3.1.5 for backward
108
+ compatibility.
109
+
8
110
  ## [0.31.0] - 2026-10-19
9
111
 
10
112
  Server-side events and DDL helpers: the ORM writes and applies `DEFINE EVENT` and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: surreal-orm-lite
3
- Version: 0.31.0
3
+ Version: 0.31.2
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
@@ -732,10 +732,11 @@ cancelled when the block ends, nothing of the block is committed, and leaving th
732
732
  `SurrealDbError` (`SurrealDbConflictError` for a retryable conflict, so `retry_on_conflict` still
733
733
  retries the block). A buffered transaction fails as a whole at commit; an interactive one raises
734
734
  the failed statement's error at once, then, when the block ends, "The transaction was cancelled: a
735
- statement in it failed; nothing was committed (…)", chained to that error. An explicit
736
- `tx.commit()` after a failure raises the same error. When the block's own exception propagates,
737
- that exception wins, as before. Reads the ORM answers itself are not failed statements: a table
738
- that does not exist reads as no rows, and `get()` raises `SurrealDbNotFoundError`.
735
+ statement in it failed; nothing was committed (SurrealDbError)", which names the type of that
736
+ error and is chained to it. An explicit `tx.commit()` after a failure raises the same error. When
737
+ the block's own exception propagates, that exception wins, as before. Reads the ORM answers itself
738
+ are not failed statements: a table that does not exist reads as no rows, and `get()` raises
739
+ `SurrealDbNotFoundError`.
739
740
 
740
741
  ```python
741
742
  async with SurrealDBConnectionManager.transaction() as tx:
@@ -747,6 +748,18 @@ async with SurrealDBConnectionManager.transaction() as tx:
747
748
  # leaving the block raised SurrealDbError: alice was not saved either
748
749
  ```
749
750
 
751
+ **Transaction errors leave out the server's excerpt of the statement** (v0.31.2). When the server
752
+ refuses the query as a whole (a statement it cannot parse), the error gives the server's reason and
753
+ the position, without the server's excerpt of the statement: a buffered commit raises "Transaction
754
+ failed: the server refused statement 2 of 3: … (at line 1, column 7 of the statement)", the
755
+ embedded engine included, and a write or a definition in an interactive transaction raises
756
+ "Transaction failed: the server refused the statement: … (at line 1, column 7 of the statement)".
757
+ The line and column are those of the statement as written, before the ORM renames its `$name`
758
+ parameters for the batch, and a delimiter left unclosed names the statement it opens in, even when
759
+ the server stops in a later statement. The server's full text stays reachable as the error's
760
+ `__context__`, which a traceback does not print. A read in a transaction is not affected: it raises
761
+ the SDK's own error, as it does outside a transaction.
762
+
750
763
  ### 12. Upsert & get_or_create / update_or_create
751
764
 
752
765
  ```python
@@ -891,7 +904,11 @@ except SurrealDbConflictError:
891
904
  - Total attempts = `max_retries + 1`; after they are exhausted the conflict is re-raised as
892
905
  `SurrealDbConflictError`. The numeric parameters are validated at decoration time.
893
906
  - Detection anchors on SurrealDB's own retryable marker ("This transaction can be retried"), so
894
- a non-retryable failure (e.g. a duplicate-key error) is **not** retried.
907
+ a non-retryable failure (e.g. a duplicate-key error) is **not** retried. `is_conflict_error()`
908
+ reads the exception and its chain as a traceback prints it: a context suppressed with
909
+ `raise … from None` is not read (v0.31.2), so a conflict you re-raise as another exception with
910
+ `raise … from None` is no longer recognised: chain it (`raise … from exc`, or `raise …` without
911
+ `from` inside the `except` block), or raise a `SurrealDbConflictError`.
895
912
  - The exception type is identical on 2.6.x and 3.x; conflicts simply arise more often on 3.x
896
913
  (optimistic MVCC) than on 2.6.x (see the behaviour table), except on change-feed tables, where
897
914
  2.x refuses concurrent writes that 3.x accepts (see [section 22](#22-change-feeds-changes)).
@@ -2499,31 +2516,42 @@ an `IntEnum` gives the value), so use `.value` to build a key, a URL or a log li
2499
2516
  #### Values the SDK cannot read
2500
2517
 
2501
2518
  The surrealdb SDK 2.0.0 cannot decode a zero duration, a SurrealDB 3.x native set (a
2502
- `TYPE set<…>` column on SurrealDB 3.3, see [Sets](#sets)) or a degenerate geometry (see
2503
- [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)): one such value fails the whole
2504
- response it is in. The clients the ORM opens read them anyway, through a hook around the SDK's own
2505
- CBOR decoder that handles only these values; everything else reads exactly as the SDK reads it:
2519
+ `TYPE set<…>` column on SurrealDB 3.3, see [Sets](#sets)), a degenerate geometry (see
2520
+ [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)) or a datetime outside years
2521
+ 1-9999, the years a Python `datetime` holds (SurrealDB stores years -262143 to +262142, on both
2522
+ lines): one such value fails the whole response it is in. The clients the ORM opens read them
2523
+ anyway, through a hook around the SDK's own CBOR decoder that handles only these values; everything
2524
+ else reads exactly as the SDK reads it:
2506
2525
 
2507
2526
  - a zero duration reads as zero: `timedelta(0)` or `Duration(0)` in a typed field, the SDK's
2508
2527
  `Duration(0)` in an untyped one;
2509
2528
  - a native set reads as a Python `set`, which `set[X]` and `frozenset[X]` fields validate, or as a
2510
2529
  `list` when its items are unhashable (record ids, objects, nested sets) or two of them are equal
2511
- in Python, like `1` and `true`: every item the server holds comes back.
2530
+ in Python, like `1` and `true`: every item the server holds comes back;
2531
+ - a datetime before `0001-01-01T00:00:00Z` or after `9999-12-31T23:59:59.999999999Z` reads as its
2532
+ RFC 3339 text, the one SurrealDB gives it (`<string>`): `"0000-06-01T00:00:00Z"`,
2533
+ `"-0001-01-01T00:00:00Z"`, `"+10000-01-01T00:00:00.500Z"` (a sign and at least four digits outside
2534
+ years 0000-9999, every nanosecond kept). An untyped field, `raw_query()` and live notifications
2535
+ give that string; a `datetime` field refuses it with a `ValidationError` naming the field: `get()`
2536
+ raises it, and `filter().exec()` and `raw_query()` return such rows as dicts, as for any row that
2537
+ does not validate. A datetime within years 1-9999, before 1970 included, reads as the SDK's
2538
+ `datetime`, as before.
2512
2539
 
2513
2540
  Written back through the ORM (`merge(v=row.v)`, or `update()`, which rewrites the whole row, the
2514
2541
  untouched fields included), a zero duration stays a duration, but a native set is stored as an
2515
- array, like every set the ORM writes, unless its column is `TYPE set<…>` (see [Sets](#sets)), and
2516
- a degenerate geometry as a plain object (see
2517
- [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)). In an untyped field, `update()`
2518
- flattens the SDK's `Duration(0)` into an object, like any SDK value there
2542
+ array, like every set the ORM writes, unless its column is `TYPE set<…>` (see [Sets](#sets)), a
2543
+ degenerate geometry as a plain object (see
2544
+ [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)), and a datetime outside years
2545
+ 1-9999 as its text, a string. In an untyped field, `update()` flattens the SDK's `Duration(0)` into
2546
+ an object, like any SDK value there
2519
2547
  ([#222](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/222)).
2520
2548
 
2521
2549
  The hook is installed only while the installed SDK cannot decode these values, and only on the
2522
2550
  ORM's own clients (`SurrealDBConnectionManager.get_client()`, `ephemeral_client()`), the embedded
2523
2551
  engine's included (see [Embedded engine and test fixtures](#26-embedded-engine-and-test-fixtures)):
2524
- there a zero duration and a degenerate geometry read as on a server, and `<set>[…]` is stored as an
2525
- array, as on 2.x. An `AsyncSurreal` client the application builds itself keeps the SDK's behaviour
2526
- and fails on them.
2552
+ there a zero duration, a degenerate geometry and a datetime outside years 1-9999 read as on a
2553
+ server, and `<set>[…]` is stored as an array, as on 2.x. An `AsyncSurreal` client the application
2554
+ builds itself keeps the SDK's behaviour and fails on them.
2527
2555
  A later SDK reads zero durations and native sets itself
2528
2556
  ([#205](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/205)), and the hook then
2529
2557
  leaves them to it.
@@ -4674,7 +4702,8 @@ await SurrealDBConnectionManager.remove_function("fn::greet")
4674
4702
  (SurrealDB 2.x, HTTP, the embedded engine) only queues the statement, so a refusal comes back when the block
4675
4703
  commits, as the transaction's own error, never as `SurrealDbAuthenticationError` or `SurrealDbNotFoundError`,
4676
4704
  `missing_ok=False` included. When a definition is applied in a transaction (`tx=`), an error raised when the
4677
- transaction ends can still carry the server's message, statement excerpt included ([#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)).
4705
+ transaction ends gives the server's reason and the statement's position, without the server's excerpt of the
4706
+ statement (v0.31.2, [#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)).
4678
4707
  - Live queries see the rows an event writes (`watch()` on their table). The query cache
4679
4708
  ([section 29](#29-query-cache-cache-querycache)) does not: it forgets the tables the ORM itself writes, so a cached
4680
4709
  read of a table that events write stays as it was until its time to live runs out. Do not cache such reads, or call
@@ -4708,9 +4737,10 @@ swallow the clauses the ORM appends, a second statement where one expression is
4708
4737
  defence: expressions are trusted application code, never user input. To use a value, read it from the record
4709
4738
  (`$after.email`) or from a `DEFINE PARAM`, never format it into the text. Kinds are parsed and written in canonical
4710
4739
  form. An `Event`'s `repr()` leaves out its expressions, and a refusal raised without `tx=` names the event or the
4711
- function, never its statement; with `tx=`, an error raised when the transaction ends can still carry the server's
4712
- message, statement excerpt included ([#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)). Applying a definition needs a privileged system user: run
4713
- `define_events()` and `define_function()` at start-up or deployment, not on a record user's session.
4740
+ function, never its statement; with `tx=`, an error raised when the transaction ends gives the server's reason and
4741
+ the statement's position, without the server's excerpt of the statement (v0.31.2,
4742
+ [#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)). Applying a definition needs a privileged
4743
+ system user: run `define_events()` and `define_function()` at start-up or deployment, not on a record user's session.
4714
4744
 
4715
4745
  #### Coming from the full SurrealDB-ORM
4716
4746
 
@@ -4849,7 +4879,7 @@ features introduced in SurrealDB 3.x. On 2.x the ORM degrades gracefully. Capabi
4849
4879
  listed behave the same on both lines. The 2.x column was measured on 2.6.5 and holds unchanged
4850
4880
  on 2.7.0. The 3.x column was measured on 3.2.4 and holds on 3.3.0, except where a row says
4851
4881
  otherwise. The full suite and every divergence below were re-verified on both new versions.
4852
- Rows added in v0.20.0 were measured directly on 2.7.0 and 3.3.0; rows added in v0.21.0 on 2.7.0 and 3.3.2, re-verified on 3.3.3, with 3.1.5 where it differs; rows added in v0.22.0 on 2.7.0 and 3.3.3, with 3.1.5 where it differs; rows added in v0.23.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.24.0 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.26.0 on 2.7.0 and 3.3.4, in memory and on versioned storage, identical on 3.1.5; rows added in v0.27.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.28.0 on 2.7.0 and 3.3.4, identical on 3.1.5; the rows added in v0.28.1 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.29.0 on 2.7.0 and 3.3.4 in one process, identical on 3.1.5. The row added in v0.29.2 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5. Rows added in v0.30.0 were measured on 2.7.0, 3.3.4 and the embedded engine, with 3.1.5 where it differs. The row added in v0.30.3 was measured on 2.7.0 and 3.3.4, over WebSocket and HTTP, and on the embedded engine, identical on 3.1.5. The row added in v0.30.7 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5: its parser refuses a statement that nests six objects, and `update()` nests at most four. Rows added in v0.31.0 were measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5.
4882
+ Rows added in v0.20.0 were measured directly on 2.7.0 and 3.3.0; rows added in v0.21.0 on 2.7.0 and 3.3.2, re-verified on 3.3.3, with 3.1.5 where it differs; rows added in v0.22.0 on 2.7.0 and 3.3.3, with 3.1.5 where it differs; rows added in v0.23.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.24.0 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.26.0 on 2.7.0 and 3.3.4, in memory and on versioned storage, identical on 3.1.5; rows added in v0.27.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.28.0 on 2.7.0 and 3.3.4, identical on 3.1.5; the rows added in v0.28.1 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.29.0 on 2.7.0 and 3.3.4 in one process, identical on 3.1.5. The row added in v0.29.2 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5. Rows added in v0.30.0 were measured on 2.7.0, 3.3.4 and the embedded engine, with 3.1.5 where it differs. The row added in v0.30.3 was measured on 2.7.0 and 3.3.4, over WebSocket and HTTP, and on the embedded engine, identical on 3.1.5. The row added in v0.30.7 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5: its parser refuses a statement that nests six objects, and `update()` nests at most four. Rows added in v0.31.0 were measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5. The row added in v0.31.1 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5.
4853
4883
  The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded engine](#orm-behaviour-the-embedded-engine).
4854
4884
 
4855
4885
  | ORM capability | SurrealDB 2.6.x / 2.7.x | SurrealDB 3.2.x / 3.3.x | Since |
@@ -4976,6 +5006,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
4976
5006
  | An event whose `THEN` fails (a `THROW`, an error) | the write is refused; the SDK's `InternalError` (`An error occurred: …` for a `THROW`, the bare cause otherwise) | the write is refused; the SDK's `InternalError` (`Error while processing event <name>: …`) | v0.31.0 |
4977
5007
  | Event and function definitions read back (`EventState`, `FunctionState`) | a block on several lines, each statement ending with `;`; `option<T>` parameters as written | a block on one line, no `;` after a single statement; optional parameters rendered with `none` first | v0.31.0 |
4978
5008
  | `define_function()`, `remove_function()` and `call_function()` | same on both lines: the next `call_function()` sees a redefinition on every connection of this process to the same namespace and database, whatever its URL | the same | v0.31.0 |
5009
+ | Reading a datetime before year 1 or after year 9999 (SurrealDB stores years -262143 to +262142) | its RFC 3339 text, as `<string>` gives it (`"+10000-01-01T00:00:00Z"`): an untyped field holds it, a `datetime` field refuses it (`ValidationError`) | same on both lines | v0.31.1 |
4979
5010
  | Full suite and every divergence in this table, re-run on SurrealDB 2.7.0 and 3.3.0 | 2.7.0 identical to 2.6.5 (same passes, same 3.x-only skips) | 3.3.0 identical to 3.2.4, except the system-user signin row | v0.19.1 |
4980
5011
 
4981
5012
  > **Note on record IDs**: A record loaded from the database has its `id` field set to a native `surrealdb.RecordID` object, not a plain string. Use `model.get_raw_id()` to obtain the bare identifier string (e.g. `"alice"`), or compare directly with `model.id == RecordID("User", "alice")`. In-memory instances you construct yourself retain whatever value you assign.
@@ -5004,7 +5035,7 @@ Capabilities not listed behave as on a 2.x server.
5004
5035
  | Anonymous session | the in-process owner (full access); `info()` returns `None` | not used by the ORM (the configured user signs in); an anonymous `info()` raises `SurrealDbAuthenticationError` | v0.25.0 |
5005
5036
  | `invalidate()` | back to the in-process owner | signs in again as the configured user | v0.25.0 |
5006
5037
  | Reading a table that does not exist | `[]`, raw queries included (as on 2.x) | the ORM reads `[]`; a raw query on 3.x raises `NotFoundError` | v0.25.0 |
5007
- | Parse errors | bare `RuntimeError` (the DDL helpers raise `SurrealDbError`) | the SDK's error classes | v0.25.0 |
5038
+ | Parse errors | bare `RuntimeError` (the DDL helpers and the commit of `transaction()` raise `SurrealDbError`) | the SDK's error classes | v0.25.0 |
5008
5039
  | Time travel (`as_of()`) | `surrealkv+versioned://` only (`?versioned=true` is ignored by SDK 2.0.0); any other URL → `SurrealDbError` before any request; `fetch()` refused (2.x core) | versioned storage required (see the 2.x/3.x table) | v0.26.0 |
5009
5040
  | Several connections (`add_connection()`) | `mem://`: each name is a database of its own; an on-disk store (`surrealkv://`, `file://`) is open under one name at a time — a second name raises `SurrealDbConnectionError` | each name opens its own clients; two databases on one server are isolated | v0.29.0 |
5010
5041
  | Schema introspection (`DatabaseIntrospector`) | reads like a 2.x server (core 2.3.10): same state and same generated model types as on the servers; the anonymous owner session may run `INFO`; `COUNT` indexes cannot be defined | need a system user (root, namespace or database user) | v0.30.0 |
@@ -681,10 +681,11 @@ cancelled when the block ends, nothing of the block is committed, and leaving th
681
681
  `SurrealDbError` (`SurrealDbConflictError` for a retryable conflict, so `retry_on_conflict` still
682
682
  retries the block). A buffered transaction fails as a whole at commit; an interactive one raises
683
683
  the failed statement's error at once, then, when the block ends, "The transaction was cancelled: a
684
- statement in it failed; nothing was committed (…)", chained to that error. An explicit
685
- `tx.commit()` after a failure raises the same error. When the block's own exception propagates,
686
- that exception wins, as before. Reads the ORM answers itself are not failed statements: a table
687
- that does not exist reads as no rows, and `get()` raises `SurrealDbNotFoundError`.
684
+ statement in it failed; nothing was committed (SurrealDbError)", which names the type of that
685
+ error and is chained to it. An explicit `tx.commit()` after a failure raises the same error. When
686
+ the block's own exception propagates, that exception wins, as before. Reads the ORM answers itself
687
+ are not failed statements: a table that does not exist reads as no rows, and `get()` raises
688
+ `SurrealDbNotFoundError`.
688
689
 
689
690
  ```python
690
691
  async with SurrealDBConnectionManager.transaction() as tx:
@@ -696,6 +697,18 @@ async with SurrealDBConnectionManager.transaction() as tx:
696
697
  # leaving the block raised SurrealDbError: alice was not saved either
697
698
  ```
698
699
 
700
+ **Transaction errors leave out the server's excerpt of the statement** (v0.31.2). When the server
701
+ refuses the query as a whole (a statement it cannot parse), the error gives the server's reason and
702
+ the position, without the server's excerpt of the statement: a buffered commit raises "Transaction
703
+ failed: the server refused statement 2 of 3: … (at line 1, column 7 of the statement)", the
704
+ embedded engine included, and a write or a definition in an interactive transaction raises
705
+ "Transaction failed: the server refused the statement: … (at line 1, column 7 of the statement)".
706
+ The line and column are those of the statement as written, before the ORM renames its `$name`
707
+ parameters for the batch, and a delimiter left unclosed names the statement it opens in, even when
708
+ the server stops in a later statement. The server's full text stays reachable as the error's
709
+ `__context__`, which a traceback does not print. A read in a transaction is not affected: it raises
710
+ the SDK's own error, as it does outside a transaction.
711
+
699
712
  ### 12. Upsert & get_or_create / update_or_create
700
713
 
701
714
  ```python
@@ -840,7 +853,11 @@ except SurrealDbConflictError:
840
853
  - Total attempts = `max_retries + 1`; after they are exhausted the conflict is re-raised as
841
854
  `SurrealDbConflictError`. The numeric parameters are validated at decoration time.
842
855
  - Detection anchors on SurrealDB's own retryable marker ("This transaction can be retried"), so
843
- a non-retryable failure (e.g. a duplicate-key error) is **not** retried.
856
+ a non-retryable failure (e.g. a duplicate-key error) is **not** retried. `is_conflict_error()`
857
+ reads the exception and its chain as a traceback prints it: a context suppressed with
858
+ `raise … from None` is not read (v0.31.2), so a conflict you re-raise as another exception with
859
+ `raise … from None` is no longer recognised: chain it (`raise … from exc`, or `raise …` without
860
+ `from` inside the `except` block), or raise a `SurrealDbConflictError`.
844
861
  - The exception type is identical on 2.6.x and 3.x; conflicts simply arise more often on 3.x
845
862
  (optimistic MVCC) than on 2.6.x (see the behaviour table), except on change-feed tables, where
846
863
  2.x refuses concurrent writes that 3.x accepts (see [section 22](#22-change-feeds-changes)).
@@ -2448,31 +2465,42 @@ an `IntEnum` gives the value), so use `.value` to build a key, a URL or a log li
2448
2465
  #### Values the SDK cannot read
2449
2466
 
2450
2467
  The surrealdb SDK 2.0.0 cannot decode a zero duration, a SurrealDB 3.x native set (a
2451
- `TYPE set<…>` column on SurrealDB 3.3, see [Sets](#sets)) or a degenerate geometry (see
2452
- [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)): one such value fails the whole
2453
- response it is in. The clients the ORM opens read them anyway, through a hook around the SDK's own
2454
- CBOR decoder that handles only these values; everything else reads exactly as the SDK reads it:
2468
+ `TYPE set<…>` column on SurrealDB 3.3, see [Sets](#sets)), a degenerate geometry (see
2469
+ [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)) or a datetime outside years
2470
+ 1-9999, the years a Python `datetime` holds (SurrealDB stores years -262143 to +262142, on both
2471
+ lines): one such value fails the whole response it is in. The clients the ORM opens read them
2472
+ anyway, through a hook around the SDK's own CBOR decoder that handles only these values; everything
2473
+ else reads exactly as the SDK reads it:
2455
2474
 
2456
2475
  - a zero duration reads as zero: `timedelta(0)` or `Duration(0)` in a typed field, the SDK's
2457
2476
  `Duration(0)` in an untyped one;
2458
2477
  - a native set reads as a Python `set`, which `set[X]` and `frozenset[X]` fields validate, or as a
2459
2478
  `list` when its items are unhashable (record ids, objects, nested sets) or two of them are equal
2460
- in Python, like `1` and `true`: every item the server holds comes back.
2479
+ in Python, like `1` and `true`: every item the server holds comes back;
2480
+ - a datetime before `0001-01-01T00:00:00Z` or after `9999-12-31T23:59:59.999999999Z` reads as its
2481
+ RFC 3339 text, the one SurrealDB gives it (`<string>`): `"0000-06-01T00:00:00Z"`,
2482
+ `"-0001-01-01T00:00:00Z"`, `"+10000-01-01T00:00:00.500Z"` (a sign and at least four digits outside
2483
+ years 0000-9999, every nanosecond kept). An untyped field, `raw_query()` and live notifications
2484
+ give that string; a `datetime` field refuses it with a `ValidationError` naming the field: `get()`
2485
+ raises it, and `filter().exec()` and `raw_query()` return such rows as dicts, as for any row that
2486
+ does not validate. A datetime within years 1-9999, before 1970 included, reads as the SDK's
2487
+ `datetime`, as before.
2461
2488
 
2462
2489
  Written back through the ORM (`merge(v=row.v)`, or `update()`, which rewrites the whole row, the
2463
2490
  untouched fields included), a zero duration stays a duration, but a native set is stored as an
2464
- array, like every set the ORM writes, unless its column is `TYPE set<…>` (see [Sets](#sets)), and
2465
- a degenerate geometry as a plain object (see
2466
- [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)). In an untyped field, `update()`
2467
- flattens the SDK's `Duration(0)` into an object, like any SDK value there
2491
+ array, like every set the ORM writes, unless its column is `TYPE set<…>` (see [Sets](#sets)), a
2492
+ degenerate geometry as a plain object (see
2493
+ [Geometries the SDK cannot read](#geometries-the-sdk-cannot-read)), and a datetime outside years
2494
+ 1-9999 as its text, a string. In an untyped field, `update()` flattens the SDK's `Duration(0)` into
2495
+ an object, like any SDK value there
2468
2496
  ([#222](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/222)).
2469
2497
 
2470
2498
  The hook is installed only while the installed SDK cannot decode these values, and only on the
2471
2499
  ORM's own clients (`SurrealDBConnectionManager.get_client()`, `ephemeral_client()`), the embedded
2472
2500
  engine's included (see [Embedded engine and test fixtures](#26-embedded-engine-and-test-fixtures)):
2473
- there a zero duration and a degenerate geometry read as on a server, and `<set>[…]` is stored as an
2474
- array, as on 2.x. An `AsyncSurreal` client the application builds itself keeps the SDK's behaviour
2475
- and fails on them.
2501
+ there a zero duration, a degenerate geometry and a datetime outside years 1-9999 read as on a
2502
+ server, and `<set>[…]` is stored as an array, as on 2.x. An `AsyncSurreal` client the application
2503
+ builds itself keeps the SDK's behaviour and fails on them.
2476
2504
  A later SDK reads zero durations and native sets itself
2477
2505
  ([#205](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/205)), and the hook then
2478
2506
  leaves them to it.
@@ -4623,7 +4651,8 @@ await SurrealDBConnectionManager.remove_function("fn::greet")
4623
4651
  (SurrealDB 2.x, HTTP, the embedded engine) only queues the statement, so a refusal comes back when the block
4624
4652
  commits, as the transaction's own error, never as `SurrealDbAuthenticationError` or `SurrealDbNotFoundError`,
4625
4653
  `missing_ok=False` included. When a definition is applied in a transaction (`tx=`), an error raised when the
4626
- transaction ends can still carry the server's message, statement excerpt included ([#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)).
4654
+ transaction ends gives the server's reason and the statement's position, without the server's excerpt of the
4655
+ statement (v0.31.2, [#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)).
4627
4656
  - Live queries see the rows an event writes (`watch()` on their table). The query cache
4628
4657
  ([section 29](#29-query-cache-cache-querycache)) does not: it forgets the tables the ORM itself writes, so a cached
4629
4658
  read of a table that events write stays as it was until its time to live runs out. Do not cache such reads, or call
@@ -4657,9 +4686,10 @@ swallow the clauses the ORM appends, a second statement where one expression is
4657
4686
  defence: expressions are trusted application code, never user input. To use a value, read it from the record
4658
4687
  (`$after.email`) or from a `DEFINE PARAM`, never format it into the text. Kinds are parsed and written in canonical
4659
4688
  form. An `Event`'s `repr()` leaves out its expressions, and a refusal raised without `tx=` names the event or the
4660
- function, never its statement; with `tx=`, an error raised when the transaction ends can still carry the server's
4661
- message, statement excerpt included ([#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)). Applying a definition needs a privileged system user: run
4662
- `define_events()` and `define_function()` at start-up or deployment, not on a record user's session.
4689
+ function, never its statement; with `tx=`, an error raised when the transaction ends gives the server's reason and
4690
+ the statement's position, without the server's excerpt of the statement (v0.31.2,
4691
+ [#278](https://github.com/EulogySnowfall/SurrealDB-ORM-lite/issues/278)). Applying a definition needs a privileged
4692
+ system user: run `define_events()` and `define_function()` at start-up or deployment, not on a record user's session.
4663
4693
 
4664
4694
  #### Coming from the full SurrealDB-ORM
4665
4695
 
@@ -4798,7 +4828,7 @@ features introduced in SurrealDB 3.x. On 2.x the ORM degrades gracefully. Capabi
4798
4828
  listed behave the same on both lines. The 2.x column was measured on 2.6.5 and holds unchanged
4799
4829
  on 2.7.0. The 3.x column was measured on 3.2.4 and holds on 3.3.0, except where a row says
4800
4830
  otherwise. The full suite and every divergence below were re-verified on both new versions.
4801
- Rows added in v0.20.0 were measured directly on 2.7.0 and 3.3.0; rows added in v0.21.0 on 2.7.0 and 3.3.2, re-verified on 3.3.3, with 3.1.5 where it differs; rows added in v0.22.0 on 2.7.0 and 3.3.3, with 3.1.5 where it differs; rows added in v0.23.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.24.0 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.26.0 on 2.7.0 and 3.3.4, in memory and on versioned storage, identical on 3.1.5; rows added in v0.27.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.28.0 on 2.7.0 and 3.3.4, identical on 3.1.5; the rows added in v0.28.1 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.29.0 on 2.7.0 and 3.3.4 in one process, identical on 3.1.5. The row added in v0.29.2 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5. Rows added in v0.30.0 were measured on 2.7.0, 3.3.4 and the embedded engine, with 3.1.5 where it differs. The row added in v0.30.3 was measured on 2.7.0 and 3.3.4, over WebSocket and HTTP, and on the embedded engine, identical on 3.1.5. The row added in v0.30.7 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5: its parser refuses a statement that nests six objects, and `update()` nests at most four. Rows added in v0.31.0 were measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5.
4831
+ Rows added in v0.20.0 were measured directly on 2.7.0 and 3.3.0; rows added in v0.21.0 on 2.7.0 and 3.3.2, re-verified on 3.3.3, with 3.1.5 where it differs; rows added in v0.22.0 on 2.7.0 and 3.3.3, with 3.1.5 where it differs; rows added in v0.23.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.24.0 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.26.0 on 2.7.0 and 3.3.4, in memory and on versioned storage, identical on 3.1.5; rows added in v0.27.0 on 2.7.0 and 3.3.4, with 3.1.5 where it differs; rows added in v0.28.0 on 2.7.0 and 3.3.4, identical on 3.1.5; the rows added in v0.28.1 on 2.7.0 and 3.3.4, identical on 3.1.5; rows added in v0.29.0 on 2.7.0 and 3.3.4 in one process, identical on 3.1.5. The row added in v0.29.2 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5. Rows added in v0.30.0 were measured on 2.7.0, 3.3.4 and the embedded engine, with 3.1.5 where it differs. The row added in v0.30.3 was measured on 2.7.0 and 3.3.4, over WebSocket and HTTP, and on the embedded engine, identical on 3.1.5. The row added in v0.30.7 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5: its parser refuses a statement that nests six objects, and `update()` nests at most four. Rows added in v0.31.0 were measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5. The row added in v0.31.1 was measured on 2.7.0, 3.3.4 and the embedded engine, identical on 3.1.5.
4802
4832
  The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded engine](#orm-behaviour-the-embedded-engine).
4803
4833
 
4804
4834
  | ORM capability | SurrealDB 2.6.x / 2.7.x | SurrealDB 3.2.x / 3.3.x | Since |
@@ -4925,6 +4955,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
4925
4955
  | An event whose `THEN` fails (a `THROW`, an error) | the write is refused; the SDK's `InternalError` (`An error occurred: …` for a `THROW`, the bare cause otherwise) | the write is refused; the SDK's `InternalError` (`Error while processing event <name>: …`) | v0.31.0 |
4926
4956
  | Event and function definitions read back (`EventState`, `FunctionState`) | a block on several lines, each statement ending with `;`; `option<T>` parameters as written | a block on one line, no `;` after a single statement; optional parameters rendered with `none` first | v0.31.0 |
4927
4957
  | `define_function()`, `remove_function()` and `call_function()` | same on both lines: the next `call_function()` sees a redefinition on every connection of this process to the same namespace and database, whatever its URL | the same | v0.31.0 |
4958
+ | Reading a datetime before year 1 or after year 9999 (SurrealDB stores years -262143 to +262142) | its RFC 3339 text, as `<string>` gives it (`"+10000-01-01T00:00:00Z"`): an untyped field holds it, a `datetime` field refuses it (`ValidationError`) | same on both lines | v0.31.1 |
4928
4959
  | Full suite and every divergence in this table, re-run on SurrealDB 2.7.0 and 3.3.0 | 2.7.0 identical to 2.6.5 (same passes, same 3.x-only skips) | 3.3.0 identical to 3.2.4, except the system-user signin row | v0.19.1 |
4929
4960
 
4930
4961
  > **Note on record IDs**: A record loaded from the database has its `id` field set to a native `surrealdb.RecordID` object, not a plain string. Use `model.get_raw_id()` to obtain the bare identifier string (e.g. `"alice"`), or compare directly with `model.id == RecordID("User", "alice")`. In-memory instances you construct yourself retain whatever value you assign.
@@ -4953,7 +4984,7 @@ Capabilities not listed behave as on a 2.x server.
4953
4984
  | Anonymous session | the in-process owner (full access); `info()` returns `None` | not used by the ORM (the configured user signs in); an anonymous `info()` raises `SurrealDbAuthenticationError` | v0.25.0 |
4954
4985
  | `invalidate()` | back to the in-process owner | signs in again as the configured user | v0.25.0 |
4955
4986
  | Reading a table that does not exist | `[]`, raw queries included (as on 2.x) | the ORM reads `[]`; a raw query on 3.x raises `NotFoundError` | v0.25.0 |
4956
- | Parse errors | bare `RuntimeError` (the DDL helpers raise `SurrealDbError`) | the SDK's error classes | v0.25.0 |
4987
+ | Parse errors | bare `RuntimeError` (the DDL helpers and the commit of `transaction()` raise `SurrealDbError`) | the SDK's error classes | v0.25.0 |
4957
4988
  | Time travel (`as_of()`) | `surrealkv+versioned://` only (`?versioned=true` is ignored by SDK 2.0.0); any other URL → `SurrealDbError` before any request; `fetch()` refused (2.x core) | versioned storage required (see the 2.x/3.x table) | v0.26.0 |
4958
4989
  | Several connections (`add_connection()`) | `mem://`: each name is a database of its own; an on-disk store (`surrealkv://`, `file://`) is open under one name at a time — a second name raises `SurrealDbConnectionError` | each name opens its own clients; two databases on one server are isolated | v0.29.0 |
4959
4990
  | Schema introspection (`DatabaseIntrospector`) | reads like a 2.x server (core 2.3.10): same state and same generated model types as on the servers; the anonymous owner session may run `INFO`; `COUNT` indexes cannot be defined | need a system user (root, namespace or database user) | v0.30.0 |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "surreal-orm-lite"
3
- version = "0.31.0"
3
+ version = "0.31.2"
4
4
  description = "Lightweight Django-style ORM for SurrealDB using the official Python SDK. Async support with Pydantic validation."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,4 +1,4 @@
1
- __version__ = "0.31.0"
1
+ __version__ = "0.31.2"
2
2
 
3
3
  from ._sdk import BoundExcluded, BoundIncluded
4
4
  from .aggregations import Aggregation, Avg, Count, Max, Min, Sum
@@ -30,12 +30,16 @@ then that module.
30
30
 
31
31
  v0.27.0 adds ``ThrownError`` (a top-level export of SDK 2.0.0): SurrealDB 3.1 reports a table that does not
32
32
  exist, read by a ``LET`` inside a block, as a ``ThrownError`` instead of a ``NotFoundError``.
33
+
34
+ #279 adds ``TAG_DATETIME_COMPACT`` (a top-level export of SDK 2.0.0, like ``TAG_DURATION_COMPACT``): the read
35
+ hook reads a datetime outside years 1-9999, which the SDK's ``tag_decoder`` refuses.
33
36
  """
34
37
 
35
38
  import importlib
36
39
  from typing import TYPE_CHECKING, Any, Final
37
40
 
38
41
  from surrealdb import ( # noqa: F401
42
+ TAG_DATETIME_COMPACT,
39
43
  TAG_DECIMAL_STRING,
40
44
  TAG_DURATION_COMPACT,
41
45
  AlreadyExistsError,
@@ -157,8 +161,9 @@ __all__ = [
157
161
  "cbor_encode",
158
162
  "cbor_decode",
159
163
  "SdkInvalidDurationError",
160
- # Read hook (#235)
164
+ # Read hook (#235); a datetime outside years 1-9999 (#279)
161
165
  "cbor_tag_decoder",
166
+ "TAG_DATETIME_COMPACT",
162
167
  "SdkAsyncWsConnection",
163
168
  "SdkAsyncHttpConnection",
164
169
  "SdkAsyncEmbeddedConnection",
@@ -42,7 +42,9 @@ def is_conflict_error(error: BaseException) -> bool:
42
42
  A :class:`SurrealDbConflictError` always qualifies. Otherwise the exception
43
43
  and its ``__cause__`` / ``__context__`` chain are scanned for one of the
44
44
  conflict signal phrases (so a conflict wrapped in another exception is still
45
- recognised).
45
+ recognised). The chain is the one a traceback prints: a context suppressed with
46
+ ``raise … from None`` is not part of it (#278) — the ORM keeps a server's full
47
+ text there, and a statement it quotes may hold conflict words.
46
48
  """
47
49
  if isinstance(error, SurrealDbConflictError):
48
50
  return True
@@ -53,7 +55,10 @@ def is_conflict_error(error: BaseException) -> bool:
53
55
  text = str(current).lower()
54
56
  if any(signal in text for signal in _CONFLICT_SIGNALS):
55
57
  return True
56
- current = current.__cause__ or current.__context__
58
+ if current.__cause__ is not None:
59
+ current = current.__cause__
60
+ else:
61
+ current = None if current.__suppress_context__ else current.__context__
57
62
  return False
58
63
 
59
64
 
@@ -1682,9 +1682,15 @@ class SurrealDBConnectionManager:
1682
1682
  raises ``SurrealDbError`` (``SurrealDbConflictError`` for a retryable conflict, so
1683
1683
  ``retry_on_conflict`` still retries). A buffered batch fails as a whole at commit; an
1684
1684
  interactive transaction raises "The transaction was cancelled: a statement in it failed;
1685
- nothing was committed (…)", chained to the failed statement's error. When the body's own
1686
- exception propagates, that exception wins. A read the ORM answers itself (a missing table
1687
- read as no rows, ``get()`` raising ``SurrealDbNotFoundError``) is not a failed statement.
1685
+ nothing was committed (SurrealDbError)", which names the type of the failed statement's
1686
+ error and is chained to it. When the body's own exception propagates, that exception wins.
1687
+ A read the ORM answers itself (a missing table read as no rows, ``get()`` raising
1688
+ ``SurrealDbNotFoundError``) is not a failed statement.
1689
+
1690
+ A query the server refuses as a whole (a statement it cannot parse) raises ``SurrealDbError``
1691
+ with the server's reason and the position, without the server's excerpt of the statement
1692
+ (issue #278): "Transaction failed: the server refused statement 2 of 3: … (at line 1, column 7
1693
+ of the statement)" at a buffered commit, the embedded engine included.
1688
1694
 
1689
1695
  SurrealDB has no savepoints/nested transactions; nested ``transaction()`` calls
1690
1696
  open INDEPENDENT transactions, not savepoints.
@@ -2488,7 +2494,7 @@ class SurrealDBConnectionManager:
2488
2494
  without *tx*, a refusal names the event and its table, never its statement, whose ``when`` and
2489
2495
  ``then`` may hold a secret). In a buffered transaction (*tx* over HTTP, on SurrealDB 2.x or the
2490
2496
  embedded engine) the statement is only queued: a refusal comes back when the block commits, as the
2491
- transaction's own error, never as ``SurrealDbAuthenticationError``. With *tx*, an error raised when the transaction ends can still carry the server's message, statement excerpt included (#278).
2497
+ transaction's own error, never as ``SurrealDbAuthenticationError``. With *tx*, an error raised when the transaction ends gives the server's reason and the statement's position, without the server's excerpt of the statement (#278).
2492
2498
  :raises SurrealDbAuthenticationError: the session may not define events.
2493
2499
  """
2494
2500
  check_call_transaction(cls._connection_name or "default", tx, operation="define_event()")
@@ -2610,7 +2616,7 @@ class SurrealDBConnectionManager:
2610
2616
  statement (``Can't apply function definition: 'fn::greet' -> …``: without *tx*, a refusal names the
2611
2617
  function, never its statement, whose body may hold a secret). In a buffered transaction (*tx* over HTTP,
2612
2618
  on SurrealDB 2.x or the embedded engine) the statement is only queued: a refusal comes back when the
2613
- block commits, as the transaction's own error, never as ``SurrealDbAuthenticationError``. With *tx*, an error raised when the transaction ends can still carry the server's message, statement excerpt included (#278).
2619
+ block commits, as the transaction's own error, never as ``SurrealDbAuthenticationError``. With *tx*, an error raised when the transaction ends gives the server's reason and the statement's position, without the server's excerpt of the statement (#278).
2614
2620
  """
2615
2621
  check_call_transaction(cls._connection_name or "default", tx, operation="define_function()")
2616
2622
  statement = function_ddl(