surreal-orm-lite 0.35.0__tar.gz → 0.35.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 (49) hide show
  1. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/CHANGELOG.md +48 -0
  2. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/PKG-INFO +34 -15
  3. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/README.md +33 -14
  4. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/pyproject.toml +1 -1
  5. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/__init__.py +1 -1
  6. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_auth.py +49 -19
  7. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_base.py +21 -21
  8. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/schema.py +17 -11
  9. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/.gitignore +0 -0
  10. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/LICENSE +0 -0
  11. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/Makefile +0 -0
  12. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/_sdk.py +0 -0
  13. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/aggregations.py +0 -0
  14. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/auth.py +0 -0
  15. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/cache.py +0 -0
  16. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/changefeed.py +0 -0
  17. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/concurrency.py +0 -0
  18. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/connection_config.py +0 -0
  19. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/connection_manager.py +0 -0
  20. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/constants.py +0 -0
  21. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/decoding.py +0 -0
  22. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/define_parser.py +0 -0
  23. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/edge.py +0 -0
  24. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/embedded.py +0 -0
  25. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/encoding.py +0 -0
  26. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/enum.py +0 -0
  27. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/exceptions.py +0 -0
  28. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/functions.py +0 -0
  29. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/geo.py +0 -0
  30. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/introspection.py +0 -0
  31. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/live.py +0 -0
  32. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_ddl.py +0 -0
  33. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_generator.py +0 -0
  34. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/py.typed +0 -0
  35. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/q.py +0 -0
  36. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/query_set.py +0 -0
  37. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/routing.py +0 -0
  38. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/schema_state.py +0 -0
  39. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/search.py +0 -0
  40. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/signals.py +0 -0
  41. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/subquery.py +0 -0
  42. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/surql_literal.py +0 -0
  43. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/testing/__init__.py +0 -0
  44. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/testing/database.py +0 -0
  45. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/testing/fixtures.py +0 -0
  46. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/time_travel.py +0 -0
  47. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/transaction.py +0 -0
  48. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/types.py +0 -0
  49. {surreal_orm_lite-0.35.0 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/utils.py +0 -0
@@ -5,6 +5,54 @@ 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.35.2] - 2026-10-22
9
+
10
+ An auth model whose signup field is declared `Field(exclude=True)` is refused whatever its schema
11
+ mode (#299): v0.34.1 refused it only for a `SCHEMAFULL` table, and on any other table its
12
+ `update()` or `upsert()` dropped the stored password hash. No new public API.
13
+
14
+ ### Changed
15
+
16
+ - **An auth model with an excluded signup field raises `TypeError` whatever its schema mode and
17
+ `with_table`** (#299). For a model built on `AuthenticatedUserMixin` with a field SIGNUP writes
18
+ that `Field(exclude=True)` keeps out of `model_dump()` (`password: str = Field(exclude=True)`),
19
+ `table_ddl()`, `define_table()`, `access_ddl()` and `define_access()` (with or without the
20
+ table) now raise `TypeError` before any request on a `SCHEMALESS` table, with no declared schema
21
+ mode and with `with_table=False`, as v0.34.1 did for a `SCHEMAFULL` table. `signup()` and
22
+ `signin()` raise the same error before any request, so an access defined by hand cannot leave
23
+ such a record either. The message names the model and the fields in declaration order and keeps
24
+ the advice: remove `exclude=True` and leave the field out where you serialise the model instead
25
+ (`model_dump(exclude={"password"})`). A model that relied on `exclude=True` now fails at
26
+ start-up instead of losing data later. Auth models without `exclude=True`, and models that are
27
+ not auth models, render and apply as before.
28
+
29
+ ### Fixed
30
+
31
+ - **An auth model no longer loses its stored password hash at `update()` or `upsert()`** (#299).
32
+ Both replace the record with what `model_dump()` writes, so an auth model whose signup field was
33
+ declared `Field(exclude=True)` lost the stored value at its first `update()` or `upsert()`, with
34
+ no error, on a `SCHEMALESS` table (a model with neither `table_type` nor `schema_mode`) or with
35
+ `define_access(with_table=False)` over an optional column defined by hand
36
+ (`TYPE option<string>`): the hash became `NONE` and every later signin failed. With a required
37
+ column (`TYPE string`), the write was refused instead. Such a model is now refused before it
38
+ writes a record (see Changed). README section 19, "User models" and a row of "ORM behaviour:
39
+ SurrealDB 2.x vs 3.x" describe it; the behaviour is the same on both lines.
40
+
41
+ ### Notes
42
+
43
+ - **Undeclared keys in list items on SurrealDB 3.x** (#301): the 0.34.1 entry below says 3.x
44
+ keeps a key a `SCHEMAFULL` table does not define "in the items of any list of objects,
45
+ `FLEXIBLE` or not". It keeps it only in the items of a list whose items define a field, such as
46
+ a list of sub-models `define_table()` writes, and in an object inside them that defines one. It
47
+ refuses it in the items of a list that defines no item field (an `array<object>` defined by hand
48
+ without `v.*.n`), at the top level and in a sub-model outside such a list. SurrealDB's
49
+ documentation of `FLEXIBLE` says every object of a `SCHEMAFULL` table, list items included,
50
+ accepts only the keys defined for it, so do not rely on 3.x keeping such a key. README section
51
+ 35, the v0.7.0 and v0.34.0 rows of "ORM behaviour: SurrealDB 2.x vs 3.x" and the embedded
52
+ engine's "Model table DDL (v0.34.0)" row now state the exception. No ORM behaviour changes.
53
+ - Server pins unchanged: tested against SurrealDB 3.3.4 and 2.7.0 and on the embedded engine,
54
+ plus 3.1.5 for backward compatibility.
55
+
8
56
  ## [0.35.0] - 2026-10-22
9
57
 
10
58
  Full-text search: a queryset searches text with the full SurrealDB-ORM's `search()`, and scores and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: surreal-orm-lite
3
- Version: 0.35.0
3
+ Version: 0.35.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
@@ -1333,6 +1333,13 @@ gives such a model the same table, so the two can be called in any order.
1333
1333
  > **The password field holds the hash.** SurrealDB returns the stored record, so
1334
1334
  > `result.user.password` is `$argon2id$v=19$…`, never the plaintext you submitted.
1335
1335
 
1336
+ **Keep the password field in `model_dump()`.** SIGNUP writes every field, while `update()` and `upsert()` replace the
1337
+ record with what `model_dump()` writes: a field declared `Field(exclude=True)` (`password: str = Field(exclude=True)`)
1338
+ would lose its stored value, the password hash, and every later signin would fail. Such a model raises `TypeError`,
1339
+ naming the fields, before any request and whatever its schema mode: `access_ddl()`, `define_access()` (with or without
1340
+ the table), `table_ddl()`, `define_table()`, `signup()` and `signin()` refuse it. Remove `exclude=True` and leave the
1341
+ field out where you serialise the model instead (`model_dump(exclude={"password"})`).
1342
+
1336
1343
  #### Renewing a session (SurrealDB 3.x only)
1337
1344
 
1338
1345
  ```python
@@ -5705,13 +5712,15 @@ print(*table, sep="\n")
5705
5712
  declares its own.
5706
5713
  - A `TableType.USER` model with `extra="allow"` raises `TypeError` there (with `with_table=True`), since its table is
5707
5714
  `SCHEMAFULL`; a `server_fields` column without a constant default needs a database `DEFAULT` ("Field types" above).
5708
- - A field SIGNUP writes that `Field(exclude=True)` keeps out of `model_dump()` (`password: str = Field(exclude=True)`)
5709
- raises `TypeError` too, naming the field, before any request (with a `SCHEMAFULL` schema mode): `define_access()`
5710
- with `with_table=True` and `define_table()` both refuse it, since the table would not define it, so SurrealDB 3.x
5711
- would refuse every signup and 2.x drop the password hash, failing every signin. Remove `exclude=True` and leave the
5712
- field out where you serialise the model instead (`model_dump(exclude={"password"})`): `update()` and `upsert()`
5713
- replace the record with what `model_dump()` writes, so even over a table defined by hand (`with_table=False`) they
5714
- would be refused (a required column) or drop the hash (an optional one), failing every later signin.
5715
+ - An auth model with a field SIGNUP writes that `Field(exclude=True)` keeps out of `model_dump()`
5716
+ (`password: str = Field(exclude=True)`) raises `TypeError` too, naming the fields, before any request and whatever
5717
+ its schema mode: `table_ddl()`, `define_table()`, `access_ddl()` and `define_access()` (with or without the table),
5718
+ `signup()` and `signin()` refuse it. `update()` and `upsert()` replace the record with what `model_dump()` writes,
5719
+ so on any table (`SCHEMALESS`, or defined by hand with `with_table=False`) they would drop the password hash, failing
5720
+ every later signin, or be refused by a required column; a `SCHEMAFULL` table would not define the field either, so
5721
+ SurrealDB 3.x would refuse every signup and 2.x drop the hash. Remove `exclude=True` and leave the field out where
5722
+ you serialise the model instead (`model_dump(exclude={"password"})`). **Changed**: such a model on a `SCHEMALESS`
5723
+ table or with `with_table=False` used to be accepted, and its first `update()` or `upsert()` dropped the hash.
5715
5724
  - Upgrading: a 0.33 `TableType.USER` model's table turns `SCHEMAFULL` at its next `define_access()`; records already
5716
5725
  there are checked at their next write.
5717
5726
 
@@ -5743,12 +5752,21 @@ print(*table, sep="\n")
5743
5752
  `define_table()` writes such a list `FLEXIBLE` before 3.x; the item's typed fields are still checked. 3.x keeps those
5744
5753
  values without it. On 2.x and the embedded engine, the items of such a list then also keep a key the model does not
5745
5754
  declare, written raw or by another client (the ORM never sends one), which they drop from a list that is not
5746
- `FLEXIBLE`: a 2.x limitation, since `FLEXIBLE` there covers the whole item. 3.x keeps such a key in the items of any
5747
- list of objects, `FLEXIBLE` or not, and refuses it at the top level or in a sub-model.
5755
+ `FLEXIBLE`: a 2.x limitation, since `FLEXIBLE` there covers the whole item. 3.x keeps such a key, `FLEXIBLE` or not,
5756
+ in the items of a list of objects whose items define a field (every list of sub-models `define_table()` writes) and
5757
+ in an object inside them that defines one; it refuses it at the top level, in a sub-model outside such a list, and in
5758
+ the items of a list that defines no item field (an `array<object>` defined by hand without `v.*.n`). SurrealDB's
5759
+ [documentation of
5760
+ `FLEXIBLE`](https://surrealdb.com/docs/reference/query-language/statements/define/field#flexible-data-types) says
5761
+ every object of a `SCHEMAFULL` table, an item of a list included, accepts only the keys defined for it unless its
5762
+ field is `FLEXIBLE`: 3.x departs from it in a list whose items define a field, so a later 3.x may refuse such a key
5763
+ there too: do not rely on it.
5748
5764
  - On SurrealDB 3.x a required untyped array refuses an object item unless its items are defined
5749
5765
  (`no such field exists`): `define_table()` writes the item definition `TYPE any` (`bag.*`) on every line.
5750
5766
  - A write naming a field a `SCHEMAFULL` table does not define is refused on 3.x and dropped on 2.x and the embedded
5751
- engine ("What SurrealDB checks" above).
5767
+ engine ("What SurrealDB checks" above), except in the items of a list of objects: on 3.x of a list whose items define
5768
+ a field (every list of sub-models `define_table()` writes), on 2.x and the embedded engine of a list `define_table()`
5769
+ writes `FLEXIBLE`.
5752
5770
  - `COMPUTED` fields are SurrealDB 3.x's: `define_table()` keeps a stored one `COMPUTED`, and writes a new computed
5753
5771
  field `VALUE` on both lines.
5754
5772
  - The [2.x/3.x table](#orm-behaviour-surrealdb-2x-vs-3x) and the
@@ -6249,7 +6267,7 @@ features introduced in SurrealDB 3.x. On 2.x the ORM degrades gracefully. Capabi
6249
6267
  listed behave the same on both lines. The 2.x column was measured on 2.6.5 and holds unchanged
6250
6268
  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
6251
6269
  otherwise. The full suite and every divergence below were re-verified on both new versions.
6252
- 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. Rows added in v0.32.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 but accepts raw writes to a view. Rows added in v0.33.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4. Rows added in v0.34.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 except that it refuses a Python `set` written to a `set` field, and an object item of an optional untyped `list`. Rows added in v0.35.0 were measured on 2.7.0, 3.3.4 and the embedded engine, with 3.1.5 where it differs.
6270
+ 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. Rows added in v0.32.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 but accepts raw writes to a view. Rows added in v0.33.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4. Rows added in v0.34.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 except that it refuses a Python `set` written to a `set` field, and an object item of an optional untyped `list`. Rows added in v0.35.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.35.2 was measured on 2.7.0 and 3.3.4, identical on 3.1.5.
6253
6271
  The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded engine](#orm-behaviour-the-embedded-engine).
6254
6272
 
6255
6273
  | ORM capability | SurrealDB 2.6.x / 2.7.x | SurrealDB 3.2.x / 3.3.x | Since |
@@ -6263,7 +6281,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6263
6281
  | Cleanup on a missing target (`delete_table`) | native no-op | ORM makes it a silent no-op | v0.7.0 |
6264
6282
  | Aggregation over an empty set (`NaN` / `±inf`) | returns `0.0` / `None` | ORM normalises to `0.0` / `None` | v0.7.0 |
6265
6283
  | Namespace/db selection (`use()` ordering) | lenient (auto-creates) | strict — ORM signs in before `use()` | v0.7.0 |
6266
- | Writing a field a `SCHEMAFULL` table does not define (`CREATE`, `INSERT`, `INSERT RELATION`) | silently dropped: the record is stored without it | rejected ("Found field …, but no such field exists"): nothing is stored | v0.7.0 |
6284
+ | Writing a field a `SCHEMAFULL` table does not define (`CREATE`, `INSERT`, `INSERT RELATION`) | silently dropped: the record is stored without it; kept in the items of a list `define_table()` writes `FLEXIBLE` (a 2.x limitation) | rejected ("Found field …, but no such field exists"): nothing is stored, except in the items of a list whose items define a field (every list of sub-models), which keep it | v0.7.0 |
6267
6285
  | `upsert()` / `update_or_create()` / `get_or_create()` | same on both lines | same on both lines | v0.10.0 |
6268
6286
  | `patch()` / `atomic_append` / `atomic_set_add` / `atomic_remove` / `atomic_increment` | same on both lines (portable `array::*` fns chosen over divergent `+=`/`-=`) | same on both lines | v0.11.0 |
6269
6287
  | `retry_on_conflict` / `SurrealDbConflictError` (retryable conflict) | same type + decorator; conflicts rarer (engine serialises more), except on `CHANGEFEED` tables (section 22) | same type + decorator; conflicts are the normal optimistic-MVCC failure | v0.12.0 |
@@ -6391,7 +6409,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6391
6409
  | Model table DDL (`define_table()`: `DEFINE TABLE` + `DEFINE FIELD`) | same on both lines: one `DEFINE TABLE OVERWRITE` and one `DEFINE FIELD OVERWRITE` per column, keeping what the model does not declare | the same | v0.34.0 |
6392
6410
  | An `Any` or untyped `list`/`set` field of a `SCHEMAFULL` model | `define_table()` adds `FLEXIBLE` (read from `client.version()`): without it these engines empty such values, optional or not; the item definitions as on 3.x | kept without `FLEXIBLE` (refused there); an untyped `list` gets an item definition `TYPE any`, without which 3.x refuses an object item of a required one (`no such field exists`) | v0.34.0 |
6393
6411
  | A `list` of sub-models (`list[M]`, at any depth) holding a `dict`, an `Any` or an untyped `list`, in a `SCHEMAFULL` model | `define_table()` also writes the `list` `FLEXIBLE`: without it these engines drop such a value from every item, then refuse the record's next write (`Found NONE for field`) | kept without `FLEXIBLE` on the `list`, which `define_table()` does not write there | v0.34.0 |
6394
- | A write naming a field a `SCHEMAFULL` table does not define (an undeclared `merge()` key, a raw query, records predating `SCHEMAFULL`) | the field is dropped, silently; kept in the items of a list `define_table()` writes `FLEXIBLE` (a 2.x limitation) | the write is refused (`no such field exists`), except in the items of a list of objects, which keep it | v0.34.0 |
6412
+ | A write naming a field a `SCHEMAFULL` table does not define (an undeclared `merge()` key, a raw query, records predating `SCHEMAFULL`) | the field is dropped, silently; kept in the items of a list `define_table()` writes `FLEXIBLE` (a 2.x limitation) | the write is refused (`no such field exists`), except in the items of a list whose items define a field (every list of sub-models `define_table()` writes), which keep it | v0.34.0 |
6395
6413
  | Full-text index (`define_indexes()`, `index_ddl(line=)`) | `SEARCH ANALYZER` (`FULLTEXT` is a parse error), read from `client.version()`; several fields accepted, the ORM writes one | `FULLTEXT ANALYZER` (`SEARCH` is a parse error); one field per index | v0.35.0 |
6396
6414
  | Removing an analyzer an index uses (`remove_analyzer()`) | the server would accept it, then refuse every write to the table: the ORM refuses it first, with 3.x's message | refused by the server | v0.35.0 |
6397
6415
  | BM25 score of a term in more than half the documents (`SearchScore`) | below zero: a text holding such a term scores lower than without it, and ranks differently than on 3.x | `0.0`: the term adds nothing to the score | v0.35.0 |
@@ -6399,6 +6417,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6399
6417
  | Two `search()` fields AND-ed | only the records matching every field, each field scored as if searched alone | the same on 3.3; 3.1 can also return records that match only one of the fields, scored `0.0` on the other | v0.35.0 |
6400
6418
  | `search()` on a field without a full-text index | `SurrealDbError` naming the field | the same; 3.1 answers no record | v0.35.0 |
6401
6419
  | `live()` with a `Subquery` that searches a field without a full-text index | `SurrealDbError` naming the field, before the live query starts | the same (the live query would never notify); 3.1 starts it | v0.35.0 |
6420
+ | An auth model with a `Field(exclude=True)` field SIGNUP writes (`password: str = Field(exclude=True)`) | `TypeError` before any request, whatever the schema mode and `with_table`: the four DDL methods, `signup()` and `signin()` | same on both lines (a check of the model, no request) | v0.35.2 |
6402
6421
  | 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 |
6403
6422
 
6404
6423
  > **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.
@@ -6434,7 +6453,7 @@ Capabilities not listed behave as on a 2.x server.
6434
6453
  | Server-side events and stored functions (v0.31.0) | as on a 2.x server: `$input` is `NONE`; `Event(async_mode=True)` raises `SurrealDbError` before any DDL | `ASYNC` events on 3.x | v0.31.0 |
6435
6454
  | Materialized views (v0.32.0) | as on a 2.x server: `int` sums and `Decimal` means; in a grouped view, `math::stddev()`/`math::variance()`, an expression around an aggregate, a field outside `GROUP BY` and a `GROUP BY` on an alias raise `SurrealDbError` before any DDL; writes to a view refused by the ORM (the engine would accept them); no live queries | `float` sums and means on 3.x | v0.32.0 |
6436
6455
  | TYPE RELATION constraints (v0.33.0) | as on a 2.x server: a raw `ALTER TABLE … TYPE` of a table already written to is not enforced (`define_relation()` sends `DEFINE TABLE OVERWRITE`); an endpoint outside `IN`/`OUT` whose record does not exist raises `SurrealDbNotFoundError` | `SurrealDbError` for that endpoint on 3.x | v0.33.0 |
6437
- | Model table DDL (v0.34.0) | as on a 2.x server: `define_table()` adds `FLEXIBLE` to the `Any` and untyped columns of a `SCHEMAFULL` table and to its lists of sub-models holding a `FLEXIBLE` column; a write naming a field the table does not define drops that field | 3.x keeps those values without `FLEXIBLE` and refuses that write | v0.34.0 |
6456
+ | Model table DDL (v0.34.0) | as on a 2.x server: `define_table()` adds `FLEXIBLE` to the `Any` and untyped columns of a `SCHEMAFULL` table and to its lists of sub-models holding a `FLEXIBLE` column; a write naming a field the table does not define drops that field, except in the items of a list `define_table()` writes `FLEXIBLE`, which keep it | 3.x keeps those values without `FLEXIBLE` and refuses that write, except in list items that define a field | v0.34.0 |
6438
6457
  | Full-text search (v0.35.0) | as on a 2.x server: `SEARCH ANALYZER`, a term in more than half the documents scores below zero, BM25 statistics grow with each rebuild and keep deleted records, `remove_analyzer()` refuses an analyzer an index uses; an index defined in the transaction that defines its analyzer sees it, which `define_indexes(tx=…)` refuses all the same | 3.x writes `FULLTEXT ANALYZER`, scores such a term `0.0`, keeps BM25 statistics exact and refuses that removal | v0.35.0 |
6439
6458
  | Everything else | CRUD, QuerySet, aggregations, relations, typed edges, rich and geo fields, upsert/patch, server values, computed fields, stored functions, signals: the same as on a 2.x server | unchanged | v0.25.0 |
6440
6459
 
@@ -1282,6 +1282,13 @@ gives such a model the same table, so the two can be called in any order.
1282
1282
  > **The password field holds the hash.** SurrealDB returns the stored record, so
1283
1283
  > `result.user.password` is `$argon2id$v=19$…`, never the plaintext you submitted.
1284
1284
 
1285
+ **Keep the password field in `model_dump()`.** SIGNUP writes every field, while `update()` and `upsert()` replace the
1286
+ record with what `model_dump()` writes: a field declared `Field(exclude=True)` (`password: str = Field(exclude=True)`)
1287
+ would lose its stored value, the password hash, and every later signin would fail. Such a model raises `TypeError`,
1288
+ naming the fields, before any request and whatever its schema mode: `access_ddl()`, `define_access()` (with or without
1289
+ the table), `table_ddl()`, `define_table()`, `signup()` and `signin()` refuse it. Remove `exclude=True` and leave the
1290
+ field out where you serialise the model instead (`model_dump(exclude={"password"})`).
1291
+
1285
1292
  #### Renewing a session (SurrealDB 3.x only)
1286
1293
 
1287
1294
  ```python
@@ -5654,13 +5661,15 @@ print(*table, sep="\n")
5654
5661
  declares its own.
5655
5662
  - A `TableType.USER` model with `extra="allow"` raises `TypeError` there (with `with_table=True`), since its table is
5656
5663
  `SCHEMAFULL`; a `server_fields` column without a constant default needs a database `DEFAULT` ("Field types" above).
5657
- - A field SIGNUP writes that `Field(exclude=True)` keeps out of `model_dump()` (`password: str = Field(exclude=True)`)
5658
- raises `TypeError` too, naming the field, before any request (with a `SCHEMAFULL` schema mode): `define_access()`
5659
- with `with_table=True` and `define_table()` both refuse it, since the table would not define it, so SurrealDB 3.x
5660
- would refuse every signup and 2.x drop the password hash, failing every signin. Remove `exclude=True` and leave the
5661
- field out where you serialise the model instead (`model_dump(exclude={"password"})`): `update()` and `upsert()`
5662
- replace the record with what `model_dump()` writes, so even over a table defined by hand (`with_table=False`) they
5663
- would be refused (a required column) or drop the hash (an optional one), failing every later signin.
5664
+ - An auth model with a field SIGNUP writes that `Field(exclude=True)` keeps out of `model_dump()`
5665
+ (`password: str = Field(exclude=True)`) raises `TypeError` too, naming the fields, before any request and whatever
5666
+ its schema mode: `table_ddl()`, `define_table()`, `access_ddl()` and `define_access()` (with or without the table),
5667
+ `signup()` and `signin()` refuse it. `update()` and `upsert()` replace the record with what `model_dump()` writes,
5668
+ so on any table (`SCHEMALESS`, or defined by hand with `with_table=False`) they would drop the password hash, failing
5669
+ every later signin, or be refused by a required column; a `SCHEMAFULL` table would not define the field either, so
5670
+ SurrealDB 3.x would refuse every signup and 2.x drop the hash. Remove `exclude=True` and leave the field out where
5671
+ you serialise the model instead (`model_dump(exclude={"password"})`). **Changed**: such a model on a `SCHEMALESS`
5672
+ table or with `with_table=False` used to be accepted, and its first `update()` or `upsert()` dropped the hash.
5664
5673
  - Upgrading: a 0.33 `TableType.USER` model's table turns `SCHEMAFULL` at its next `define_access()`; records already
5665
5674
  there are checked at their next write.
5666
5675
 
@@ -5692,12 +5701,21 @@ print(*table, sep="\n")
5692
5701
  `define_table()` writes such a list `FLEXIBLE` before 3.x; the item's typed fields are still checked. 3.x keeps those
5693
5702
  values without it. On 2.x and the embedded engine, the items of such a list then also keep a key the model does not
5694
5703
  declare, written raw or by another client (the ORM never sends one), which they drop from a list that is not
5695
- `FLEXIBLE`: a 2.x limitation, since `FLEXIBLE` there covers the whole item. 3.x keeps such a key in the items of any
5696
- list of objects, `FLEXIBLE` or not, and refuses it at the top level or in a sub-model.
5704
+ `FLEXIBLE`: a 2.x limitation, since `FLEXIBLE` there covers the whole item. 3.x keeps such a key, `FLEXIBLE` or not,
5705
+ in the items of a list of objects whose items define a field (every list of sub-models `define_table()` writes) and
5706
+ in an object inside them that defines one; it refuses it at the top level, in a sub-model outside such a list, and in
5707
+ the items of a list that defines no item field (an `array<object>` defined by hand without `v.*.n`). SurrealDB's
5708
+ [documentation of
5709
+ `FLEXIBLE`](https://surrealdb.com/docs/reference/query-language/statements/define/field#flexible-data-types) says
5710
+ every object of a `SCHEMAFULL` table, an item of a list included, accepts only the keys defined for it unless its
5711
+ field is `FLEXIBLE`: 3.x departs from it in a list whose items define a field, so a later 3.x may refuse such a key
5712
+ there too: do not rely on it.
5697
5713
  - On SurrealDB 3.x a required untyped array refuses an object item unless its items are defined
5698
5714
  (`no such field exists`): `define_table()` writes the item definition `TYPE any` (`bag.*`) on every line.
5699
5715
  - A write naming a field a `SCHEMAFULL` table does not define is refused on 3.x and dropped on 2.x and the embedded
5700
- engine ("What SurrealDB checks" above).
5716
+ engine ("What SurrealDB checks" above), except in the items of a list of objects: on 3.x of a list whose items define
5717
+ a field (every list of sub-models `define_table()` writes), on 2.x and the embedded engine of a list `define_table()`
5718
+ writes `FLEXIBLE`.
5701
5719
  - `COMPUTED` fields are SurrealDB 3.x's: `define_table()` keeps a stored one `COMPUTED`, and writes a new computed
5702
5720
  field `VALUE` on both lines.
5703
5721
  - The [2.x/3.x table](#orm-behaviour-surrealdb-2x-vs-3x) and the
@@ -6198,7 +6216,7 @@ features introduced in SurrealDB 3.x. On 2.x the ORM degrades gracefully. Capabi
6198
6216
  listed behave the same on both lines. The 2.x column was measured on 2.6.5 and holds unchanged
6199
6217
  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
6200
6218
  otherwise. The full suite and every divergence below were re-verified on both new versions.
6201
- 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. Rows added in v0.32.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 but accepts raw writes to a view. Rows added in v0.33.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4. Rows added in v0.34.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 except that it refuses a Python `set` written to a `set` field, and an object item of an optional untyped `list`. Rows added in v0.35.0 were measured on 2.7.0, 3.3.4 and the embedded engine, with 3.1.5 where it differs.
6219
+ 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. Rows added in v0.32.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 but accepts raw writes to a view. Rows added in v0.33.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4. Rows added in v0.34.0 were measured on 2.7.0, 3.3.4 and the embedded engine; 3.1.5 behaves as 3.3.4 except that it refuses a Python `set` written to a `set` field, and an object item of an optional untyped `list`. Rows added in v0.35.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.35.2 was measured on 2.7.0 and 3.3.4, identical on 3.1.5.
6202
6220
  The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded engine](#orm-behaviour-the-embedded-engine).
6203
6221
 
6204
6222
  | ORM capability | SurrealDB 2.6.x / 2.7.x | SurrealDB 3.2.x / 3.3.x | Since |
@@ -6212,7 +6230,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6212
6230
  | Cleanup on a missing target (`delete_table`) | native no-op | ORM makes it a silent no-op | v0.7.0 |
6213
6231
  | Aggregation over an empty set (`NaN` / `±inf`) | returns `0.0` / `None` | ORM normalises to `0.0` / `None` | v0.7.0 |
6214
6232
  | Namespace/db selection (`use()` ordering) | lenient (auto-creates) | strict — ORM signs in before `use()` | v0.7.0 |
6215
- | Writing a field a `SCHEMAFULL` table does not define (`CREATE`, `INSERT`, `INSERT RELATION`) | silently dropped: the record is stored without it | rejected ("Found field …, but no such field exists"): nothing is stored | v0.7.0 |
6233
+ | Writing a field a `SCHEMAFULL` table does not define (`CREATE`, `INSERT`, `INSERT RELATION`) | silently dropped: the record is stored without it; kept in the items of a list `define_table()` writes `FLEXIBLE` (a 2.x limitation) | rejected ("Found field …, but no such field exists"): nothing is stored, except in the items of a list whose items define a field (every list of sub-models), which keep it | v0.7.0 |
6216
6234
  | `upsert()` / `update_or_create()` / `get_or_create()` | same on both lines | same on both lines | v0.10.0 |
6217
6235
  | `patch()` / `atomic_append` / `atomic_set_add` / `atomic_remove` / `atomic_increment` | same on both lines (portable `array::*` fns chosen over divergent `+=`/`-=`) | same on both lines | v0.11.0 |
6218
6236
  | `retry_on_conflict` / `SurrealDbConflictError` (retryable conflict) | same type + decorator; conflicts rarer (engine serialises more), except on `CHANGEFEED` tables (section 22) | same type + decorator; conflicts are the normal optimistic-MVCC failure | v0.12.0 |
@@ -6340,7 +6358,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6340
6358
  | Model table DDL (`define_table()`: `DEFINE TABLE` + `DEFINE FIELD`) | same on both lines: one `DEFINE TABLE OVERWRITE` and one `DEFINE FIELD OVERWRITE` per column, keeping what the model does not declare | the same | v0.34.0 |
6341
6359
  | An `Any` or untyped `list`/`set` field of a `SCHEMAFULL` model | `define_table()` adds `FLEXIBLE` (read from `client.version()`): without it these engines empty such values, optional or not; the item definitions as on 3.x | kept without `FLEXIBLE` (refused there); an untyped `list` gets an item definition `TYPE any`, without which 3.x refuses an object item of a required one (`no such field exists`) | v0.34.0 |
6342
6360
  | A `list` of sub-models (`list[M]`, at any depth) holding a `dict`, an `Any` or an untyped `list`, in a `SCHEMAFULL` model | `define_table()` also writes the `list` `FLEXIBLE`: without it these engines drop such a value from every item, then refuse the record's next write (`Found NONE for field`) | kept without `FLEXIBLE` on the `list`, which `define_table()` does not write there | v0.34.0 |
6343
- | A write naming a field a `SCHEMAFULL` table does not define (an undeclared `merge()` key, a raw query, records predating `SCHEMAFULL`) | the field is dropped, silently; kept in the items of a list `define_table()` writes `FLEXIBLE` (a 2.x limitation) | the write is refused (`no such field exists`), except in the items of a list of objects, which keep it | v0.34.0 |
6361
+ | A write naming a field a `SCHEMAFULL` table does not define (an undeclared `merge()` key, a raw query, records predating `SCHEMAFULL`) | the field is dropped, silently; kept in the items of a list `define_table()` writes `FLEXIBLE` (a 2.x limitation) | the write is refused (`no such field exists`), except in the items of a list whose items define a field (every list of sub-models `define_table()` writes), which keep it | v0.34.0 |
6344
6362
  | Full-text index (`define_indexes()`, `index_ddl(line=)`) | `SEARCH ANALYZER` (`FULLTEXT` is a parse error), read from `client.version()`; several fields accepted, the ORM writes one | `FULLTEXT ANALYZER` (`SEARCH` is a parse error); one field per index | v0.35.0 |
6345
6363
  | Removing an analyzer an index uses (`remove_analyzer()`) | the server would accept it, then refuse every write to the table: the ORM refuses it first, with 3.x's message | refused by the server | v0.35.0 |
6346
6364
  | BM25 score of a term in more than half the documents (`SearchScore`) | below zero: a text holding such a term scores lower than without it, and ranks differently than on 3.x | `0.0`: the term adds nothing to the score | v0.35.0 |
@@ -6348,6 +6366,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6348
6366
  | Two `search()` fields AND-ed | only the records matching every field, each field scored as if searched alone | the same on 3.3; 3.1 can also return records that match only one of the fields, scored `0.0` on the other | v0.35.0 |
6349
6367
  | `search()` on a field without a full-text index | `SurrealDbError` naming the field | the same; 3.1 answers no record | v0.35.0 |
6350
6368
  | `live()` with a `Subquery` that searches a field without a full-text index | `SurrealDbError` naming the field, before the live query starts | the same (the live query would never notify); 3.1 starts it | v0.35.0 |
6369
+ | An auth model with a `Field(exclude=True)` field SIGNUP writes (`password: str = Field(exclude=True)`) | `TypeError` before any request, whatever the schema mode and `with_table`: the four DDL methods, `signup()` and `signin()` | same on both lines (a check of the model, no request) | v0.35.2 |
6351
6370
  | 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 |
6352
6371
 
6353
6372
  > **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.
@@ -6383,7 +6402,7 @@ Capabilities not listed behave as on a 2.x server.
6383
6402
  | Server-side events and stored functions (v0.31.0) | as on a 2.x server: `$input` is `NONE`; `Event(async_mode=True)` raises `SurrealDbError` before any DDL | `ASYNC` events on 3.x | v0.31.0 |
6384
6403
  | Materialized views (v0.32.0) | as on a 2.x server: `int` sums and `Decimal` means; in a grouped view, `math::stddev()`/`math::variance()`, an expression around an aggregate, a field outside `GROUP BY` and a `GROUP BY` on an alias raise `SurrealDbError` before any DDL; writes to a view refused by the ORM (the engine would accept them); no live queries | `float` sums and means on 3.x | v0.32.0 |
6385
6404
  | TYPE RELATION constraints (v0.33.0) | as on a 2.x server: a raw `ALTER TABLE … TYPE` of a table already written to is not enforced (`define_relation()` sends `DEFINE TABLE OVERWRITE`); an endpoint outside `IN`/`OUT` whose record does not exist raises `SurrealDbNotFoundError` | `SurrealDbError` for that endpoint on 3.x | v0.33.0 |
6386
- | Model table DDL (v0.34.0) | as on a 2.x server: `define_table()` adds `FLEXIBLE` to the `Any` and untyped columns of a `SCHEMAFULL` table and to its lists of sub-models holding a `FLEXIBLE` column; a write naming a field the table does not define drops that field | 3.x keeps those values without `FLEXIBLE` and refuses that write | v0.34.0 |
6405
+ | Model table DDL (v0.34.0) | as on a 2.x server: `define_table()` adds `FLEXIBLE` to the `Any` and untyped columns of a `SCHEMAFULL` table and to its lists of sub-models holding a `FLEXIBLE` column; a write naming a field the table does not define drops that field, except in the items of a list `define_table()` writes `FLEXIBLE`, which keep it | 3.x keeps those values without `FLEXIBLE` and refuses that write, except in list items that define a field | v0.34.0 |
6387
6406
  | Full-text search (v0.35.0) | as on a 2.x server: `SEARCH ANALYZER`, a term in more than half the documents scores below zero, BM25 statistics grow with each rebuild and keep deleted records, `remove_analyzer()` refuses an analyzer an index uses; an index defined in the transaction that defines its analyzer sees it, which `define_indexes(tx=…)` refuses all the same | 3.x writes `FULLTEXT ANALYZER`, scores such a term `0.0`, keeps BM25 statistics exact and refuses that removal | v0.35.0 |
6388
6407
  | Everything else | CRUD, QuerySet, aggregations, relations, typed edges, rich and geo fields, upsert/patch, server values, computed fields, stored functions, signals: the same as on a 2.x server | unchanged | v0.25.0 |
6389
6408
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "surreal-orm-lite"
3
- version = "0.35.0"
3
+ version = "0.35.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.35.0"
1
+ __version__ = "0.35.2"
2
2
 
3
3
  from ._sdk import BoundExcluded, BoundIncluded
4
4
  from .aggregations import Aggregation, Avg, Count, Max, Min, Sum
@@ -38,7 +38,7 @@ from .auth import AuthTokens, build_auth_payload, wrap_auth_error
38
38
  from .connection_manager import SurrealDBConnectionManager
39
39
  from .embedded import model_auth_unsupported
40
40
  from .exceptions import SurrealDbAuthenticationError, SurrealDbError
41
- from .schema import TableClauses, declared_clauses
41
+ from .schema import TableClauses, declared_clauses, signup_excluded_refused
42
42
  from .transaction import Transaction
43
43
  from .types import SchemaMode
44
44
  from .utils import apply_ddl_statements, validate_alias_name, validate_field_name
@@ -268,17 +268,33 @@ class AuthenticatedUserMixin:
268
268
  @classmethod
269
269
  def _signup_fields_excluded(cls) -> list[str]:
270
270
  """The fields SIGNUP writes that ``Field(exclude=True)`` keeps out of ``model_dump()``, in declaration order
271
- (#296).
272
-
273
- The model table DDL defines the columns ``model_dump()`` writes (``model_ddl.model_columns()`` skips the same
274
- fields), while SIGNUP writes every field of :meth:`_signup_fields` — so on a ``SCHEMAFULL`` table these have no
275
- column: SurrealDB 3.x refuses the signup, 2.x drops their values (a password hash, and every signin fails).
276
- ``BaseSurrealModel._table_ddl()`` refuses such a table (SG1), behind ``table_ddl()``, ``define_table()``,
277
- :meth:`access_ddl` and :meth:`define_access`.
271
+ (#296, #299).
272
+
273
+ SIGNUP writes every field of :meth:`_signup_fields`, while ``update()`` and ``upsert()`` replace the stored
274
+ record with what ``model_dump()`` writes, whatever the table's schema mode — so they drop these values (a
275
+ password hash, and every later signin fails), or a required column refuses them —, and the model table DDL
276
+ defines the columns ``model_dump()`` writes (``model_ddl.model_columns()`` skips the same fields), so on a
277
+ ``SCHEMAFULL`` table these have no column: SurrealDB 3.x refuses the signup, 2.x drops their values.
278
+ :meth:`_refuse_signup_fields_excluded` refuses such a model (SG1).
278
279
  """
279
280
  fields = getattr(cls, "model_fields", {}) or {}
280
281
  return [name for name in cls._signup_fields() if fields[name].exclude is True]
281
282
 
283
+ @classmethod
284
+ def _refuse_signup_fields_excluded(cls) -> None:
285
+ """SG1 (#296, #299): refuse a model with a field SIGNUP writes that ``Field(exclude=True)`` keeps out of
286
+ ``model_dump()`` (:meth:`_signup_fields_excluded`), whatever its schema mode. Pure:
287
+ ``BaseSurrealModel._table_ddl()`` (behind ``table_ddl()`` and ``define_table()``), :meth:`access_ddl` (behind
288
+ :meth:`define_access`, with or without the table) and :meth:`_auth_variables` (behind :meth:`signup` and
289
+ :meth:`signin`) call it before any request.
290
+
291
+ Raises:
292
+ TypeError: naming the model and those fields, in declaration order (``schema.signup_excluded_refused()``).
293
+ """
294
+ excluded = cls._signup_fields_excluded()
295
+ if excluded:
296
+ raise TypeError(signup_excluded_refused(cls.__name__, excluded))
297
+
282
298
  @classmethod
283
299
  def _signup_primary_key(cls) -> str | None:
284
300
  """The primary key SIGNUP builds the record id from, or ``None`` for a server id.
@@ -383,15 +399,17 @@ class AuthenticatedUserMixin:
383
399
  TypeError: with ``with_table=True``, a ``TableType.USER`` model with
384
400
  ``extra="allow"`` (its ``SCHEMAFULL`` table refuses or drops the fields the
385
401
  model does not declare), or, for a model whose schema mode is ``SCHEMAFULL``,
386
- an *overwrite* that is not a ``bool``, or a field SIGNUP writes that
387
- ``Field(exclude=True)`` keeps out of ``model_dump()`` and so out of the table
388
- (SurrealDB 3.x would refuse every signup, 2.x drop the value — a password hash,
389
- failing every signin); the message names the model and the fields, in
390
- declaration order. ``table_ddl()`` and ``define_table()`` refuse that model too.
402
+ an *overwrite* that is not a ``bool``; then, whatever the schema mode and
403
+ *with_table*, a field SIGNUP writes that ``Field(exclude=True)`` keeps out of
404
+ ``model_dump()``: ``update()`` and ``upsert()`` replace the record with what
405
+ ``model_dump()`` writes, so on any table (``SCHEMALESS``, or defined by hand)
406
+ they would drop the value — a password hash, failing every later signin — or
407
+ be refused by a required column, and a ``SCHEMAFULL`` table would not define
408
+ the field (SurrealDB 3.x would refuse every signup, 2.x drop the value). The
409
+ message names the model and the fields, in declaration order; ``table_ddl()``,
410
+ ``define_table()``, :meth:`signup` and :meth:`signin` refuse that model too.
391
411
  Remove ``exclude=True`` and pass ``exclude=`` to ``model_dump()`` where you
392
- serialise the model: ``update()`` and ``upsert()`` replace the record with what
393
- ``model_dump()`` writes, so a table defined by hand would refuse them or drop the
394
- value.
412
+ serialise the model.
395
413
  ValueError: an auth key naming no field of the model, an unknown hashing family or
396
414
  duration, a primary key SIGNUP cannot bind; with ``with_table=True`` and a
397
415
  ``SCHEMAFULL`` schema mode, a ``FLEXIBLE`` request on a kind without an object
@@ -416,11 +434,13 @@ class AuthenticatedUserMixin:
416
434
 
417
435
  statements: list[str] = []
418
436
  if with_table and cls._schemafull_access():
419
- # The model table DDL's refusals, SG1 (#296) included: a column SIGNUP writes that the table would not define.
437
+ # The model table DDL's refusals, UX1 then SG1 included.
420
438
  table_ddl: list[str] = cls._table_ddl(overwrite, cls._access_table_clauses()) # type: ignore[attr-defined]
421
439
  statements.extend(table_ddl)
422
440
  elif with_table:
423
441
  statements.append(cls._access_table_statement(table, clause))
442
+ # SG1 whatever the schema mode and with_table (#299): update() and upsert() would drop what SIGNUP wrote.
443
+ cls._refuse_signup_fields_excluded()
424
444
 
425
445
  # The record id follows the model's primary key, because the rest of the ORM does: a
426
446
  # random id here would leave User.objects().get(email=…) addressing a record the
@@ -516,8 +536,8 @@ class AuthenticatedUserMixin:
516
536
 
517
537
  Raises:
518
538
  TypeError: see :meth:`access_ddl` — before any request, a field SIGNUP writes that
519
- ``Field(exclude=True)`` keeps out of a ``SCHEMAFULL`` table included, which
520
- ``define_table()`` refuses too.
539
+ ``Field(exclude=True)`` keeps out of ``model_dump()`` included, whatever the
540
+ schema mode and *with_table*.
521
541
  ValueError: see :meth:`access_ddl` — before any request.
522
542
  SurrealDbError: if the server rejects a statement. The most likely cause on
523
543
  **SurrealDB 2.6.x** is ``with_refresh=True``: that line cannot parse
@@ -574,7 +594,12 @@ class AuthenticatedUserMixin:
574
594
  Every field named in the SIGNUP clause must be bound, or the server has no value for a
575
595
  ``$variable`` it references — so absent optional fields are sent as ``None`` explicitly
576
596
  rather than omitted.
597
+
598
+ First of all, SG1 (#299): a model with a field SIGNUP writes that ``Field(exclude=True)``
599
+ keeps out of ``model_dump()`` is refused, so an access defined by hand cannot leave a
600
+ stored record that ``update()`` or ``upsert()`` would strip of its hash.
577
601
  """
602
+ cls._refuse_signup_fields_excluded()
578
603
  known = set(cls._signup_fields())
579
604
  unknown = sorted(set(values) - known)
580
605
  if unknown:
@@ -926,6 +951,10 @@ class AuthenticatedUserMixin:
926
951
  instance's password field holds the **hash**, since that is what the server stores.
927
952
 
928
953
  Raises:
954
+ TypeError: the model has a field SIGNUP writes that ``Field(exclude=True)`` keeps
955
+ out of ``model_dump()`` (see :meth:`access_ddl`) — before any request, so an
956
+ access defined by hand cannot leave a record that ``update()`` or ``upsert()``
957
+ would strip of its password hash.
929
958
  ValueError: for an unknown field or a missing required one — before any request.
930
959
  SurrealDbAuthenticationError: if the server refuses the signup, or returns no
931
960
  record for the new session (the message says how to grant the permission).
@@ -948,6 +977,7 @@ class AuthenticatedUserMixin:
948
977
  Isolation and ``bind=`` behave exactly as in :meth:`signup`, on the embedded engine too.
949
978
 
950
979
  Raises:
980
+ TypeError: as in :meth:`signup` — before any request.
951
981
  ValueError: for an unknown or missing field — before any request.
952
982
  SurrealDbAuthenticationError: if the credentials are refused. Both DB lines are
953
983
  normalised here: the same wrong password surfaces as ``NotFoundError`` on 3.x
@@ -47,7 +47,6 @@ from .schema import (
47
47
  read_table_fields,
48
48
  remove_event_statement,
49
49
  remove_view_statement,
50
- signup_excluded_refused,
51
50
  table_holds_records,
52
51
  table_kind,
53
52
  table_permissions_text,
@@ -1761,9 +1760,9 @@ class BaseSurrealModel(BaseModel):
1761
1760
  ``FLEXIBLE`` following *clauses*' schema mode. Pure.
1762
1761
 
1763
1762
  Raises:
1764
- TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"`` (UX1); then, when
1765
- *clauses* are ``SCHEMAFULL``, an auth model (``AuthenticatedUserMixin``) with a field SIGNUP writes that
1766
- ``Field(exclude=True)`` keeps out of the columns (SG1, #296).
1763
+ TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"`` (UX1); then,
1764
+ whatever *clauses*' schema mode, an auth model (``AuthenticatedUserMixin``) with a field SIGNUP writes
1765
+ that ``Field(exclude=True)`` keeps out of ``model_dump()`` (SG1, #296, #299).
1767
1766
  ValueError: see :meth:`_table_columns`.
1768
1767
  """
1769
1768
  if cls._is_view():
@@ -1774,16 +1773,15 @@ class BaseSurrealModel(BaseModel):
1774
1773
  raise TypeError(user_extra_refused(cls.__name__))
1775
1774
  schemafull = clauses.schema_mode == SchemaMode.SCHEMAFULL.value
1776
1775
  fields = [field_statement(table, column, overwrite=overwrite, schemafull=schemafull) for column in cls._table_columns()]
1777
- if schemafull:
1778
- # Imported here, only for a SCHEMAFULL table: model_auth sits above this module.
1779
- from .model_auth import AuthenticatedUserMixin
1780
-
1781
- # SG1 (#296), after the other refusals: SIGNUP writes a field the columns leave out, which the SCHEMAFULL
1782
- # table would refuse (3.x) or drop (2.x) — checked here, as UX1 is, so that table_ddl(), define_table(),
1783
- # access_ddl() and define_access() refuse the same model.
1784
- excluded: list[str] = cls._signup_fields_excluded() if issubclass(cls, AuthenticatedUserMixin) else []
1785
- if excluded:
1786
- raise TypeError(signup_excluded_refused(cls.__name__, excluded))
1776
+ # Imported here: model_auth sits above this module.
1777
+ from .model_auth import AuthenticatedUserMixin
1778
+
1779
+ if issubclass(cls, AuthenticatedUserMixin):
1780
+ # SG1 (#296, #299), after the other refusals, whatever the schema mode: SIGNUP writes a field model_dump()
1781
+ # leaves out, which update() and upsert() would drop and a SCHEMAFULL table would refuse (3.x) or drop
1782
+ # (2.x) — checked here, as UX1 is, so that table_ddl() and define_table() refuse the model access_ddl(),
1783
+ # define_access(), signup() and signin() refuse.
1784
+ cls._refuse_signup_fields_excluded()
1787
1785
  return [statement, *fields]
1788
1786
 
1789
1787
  @classmethod
@@ -1827,11 +1825,13 @@ class BaseSurrealModel(BaseModel):
1827
1825
  Raises:
1828
1826
  TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"`` (its table is
1829
1827
  ``SCHEMAFULL``, which refuses or drops the fields the model does not declare); an auth model
1830
- (``AuthenticatedUserMixin``) whose declared schema mode is ``SCHEMAFULL`` with a field SIGNUP writes
1831
- that ``Field(exclude=True)`` keeps out of the columns (``password: str = Field(exclude=True)``), which
1832
- the table would refuse or drop, failing every signup or signin — ``define_access()`` refuses it too;
1833
- remove ``exclude=True`` (a table defined by hand would not help: ``update()`` and ``upsert()`` replace
1834
- the record with what ``model_dump()`` writes).
1828
+ (``AuthenticatedUserMixin``), whatever its schema mode, with a field SIGNUP writes that
1829
+ ``Field(exclude=True)`` keeps out of ``model_dump()`` (``password: str = Field(exclude=True)``):
1830
+ ``update()`` and ``upsert()`` replace the record with what ``model_dump()`` writes, so they would drop
1831
+ the password hash, failing every later signin, or be refused by a required column, and a
1832
+ ``SCHEMAFULL`` table would not define the field — ``access_ddl()``, ``define_access()``, ``signup()``
1833
+ and ``signin()`` refuse the model too; remove ``exclude=True`` and pass ``exclude=`` to
1834
+ ``model_dump()`` where you serialise the model.
1835
1835
  ValueError: a ``FLEXIBLE`` request on a kind without an object; a computed field's expression that is not
1836
1836
  SurrealQL the ORM can embed.
1837
1837
 
@@ -2008,8 +2008,8 @@ class BaseSurrealModel(BaseModel):
2008
2008
 
2009
2009
  Raises:
2010
2010
  TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"``; an auth model
2011
- whose declared schema mode is ``SCHEMAFULL`` with a field SIGNUP writes that ``Field(exclude=True)``
2012
- keeps out of the columns (see :meth:`table_ddl`) — before any request.
2011
+ with a field SIGNUP writes that ``Field(exclude=True)`` keeps out of ``model_dump()``, whatever its
2012
+ schema mode (see :meth:`table_ddl`) — before any request.
2013
2013
  ValueError: a ``FLEXIBLE`` request on a kind without an object; a computed field's expression that is not
2014
2014
  SurrealQL the ORM can embed — before any request.
2015
2015
  SurrealDbError: *tx* belongs to another connection — before any request; the table is a materialized view
@@ -85,8 +85,10 @@ table's field definitions with one ``INFO FOR TABLE``, keyed by path whichever r
85
85
  definition the parser cannot read. ``table_refused()`` gives the texts of the node models' table guard (TG1–TG4),
86
86
  ``relation_table_refused()`` names ``define_table()`` when it is the caller, and ``relation_schemafull_refused()``
87
87
  refuses ``define_relation()`` of an edge model declared ``SCHEMAFULL`` with data fields, which it would not define.
88
- ``user_extra_refused()`` and ``signup_excluded_refused()`` give the texts of UX1 and SG1, the refusals of a
89
- ``SCHEMAFULL`` table the model table DDL checks before any request.
88
+ ``user_extra_refused()`` gives the text of UX1, the refusal of a ``SCHEMAFULL`` table the model table DDL checks before
89
+ any request, and ``signup_excluded_refused()`` that of SG1, an auth model with a field SIGNUP writes that
90
+ ``Field(exclude=True)`` keeps out of ``model_dump()``, refused whatever its schema mode by the model table DDL, the
91
+ access DDL, ``signup()`` and ``signin()`` (#299).
90
92
 
91
93
  The module sits low in the package: it imports neither ``model_base`` nor ``connection_manager``, so both can import
92
94
  it.
@@ -1949,16 +1951,20 @@ def user_extra_refused(model: str) -> str:
1949
1951
 
1950
1952
 
1951
1953
  def signup_excluded_refused(model: str, fields: Sequence[str]) -> str:
1952
- """The refusal of an auth model whose ``SCHEMAFULL`` table would leave out *fields* (SG1, #296): SIGNUP writes
1953
- them, ``Field(exclude=True)`` keeps them out of ``model_dump()`` and so out of the columns the model table DDL
1954
- defines — in ``table_ddl()``, ``define_table()`` and ``define_access()`` alike. Pure."""
1954
+ """The refusal of an auth model with *fields* (in declaration order) that SIGNUP writes and
1955
+ ``Field(exclude=True)`` keeps out of ``model_dump()`` (SG1): ``update()`` and ``upsert()`` replace the stored record
1956
+ with what ``model_dump()`` writes, whatever the table's schema mode, so they drop them or a required column refuses
1957
+ them (#299), and a ``SCHEMAFULL`` table defined from the model has no column for them (#296). ``table_ddl()``,
1958
+ ``define_table()``, ``access_ddl()``, ``define_access()``, ``signup()`` and ``signin()`` raise it before any
1959
+ request. Pure."""
1955
1960
  names = ", ".join(repr(name) for name in fields)
1956
1961
  pronoun = "it" if len(fields) == 1 else "them"
1957
1962
  return (
1958
- f"{model}: SIGNUP writes {names}, but Field(exclude=True) keeps {pronoun} out of model_dump() and so out of the "
1959
- f"SCHEMAFULL table that table_ddl(), define_table() and define_access() define for the model; that table would "
1960
- f"refuse {pronoun} (SurrealDB 3.x) or drop {pronoun} (2.x), so no signup or signin would work. Remove "
1961
- f"exclude=True and leave {pronoun} out where you serialise the model instead (model_dump(exclude={{{names}}})): "
1962
- f"update() and upsert() replace the record with what model_dump() writes, so even over a table defined by "
1963
- f"hand they would be refused or drop {pronoun}."
1963
+ f"{model}: SIGNUP writes {names}, but Field(exclude=True) keeps {pronoun} out of model_dump(), and update() and "
1964
+ f"upsert() replace the stored record with what model_dump() writes, whatever the table's schema mode: they "
1965
+ f"would drop {pronoun} (a password hash: every later signin fails) or be refused by a required column. A "
1966
+ f"SCHEMAFULL table defined from the model has no column for {pronoun} either (SurrealDB 3.x refuses the signup, "
1967
+ f"2.x drops {pronoun}). Remove exclude=True and leave {pronoun} out where you serialise the model instead "
1968
+ f"(model_dump(exclude={{{names}}})); until then table_ddl(), define_table(), access_ddl(), define_access(), "
1969
+ f"signup() and signin() refuse {model}."
1964
1970
  )