surreal-orm-lite 0.34.0__tar.gz → 0.34.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 (48) hide show
  1. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/CHANGELOG.md +50 -0
  2. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/PKG-INFO +28 -6
  3. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/README.md +27 -5
  4. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/pyproject.toml +1 -1
  5. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/__init__.py +1 -1
  6. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/model_auth.py +38 -5
  7. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/model_base.py +24 -5
  8. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/schema.py +18 -0
  9. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/.gitignore +0 -0
  10. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/LICENSE +0 -0
  11. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/Makefile +0 -0
  12. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/_sdk.py +0 -0
  13. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/aggregations.py +0 -0
  14. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/auth.py +0 -0
  15. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/cache.py +0 -0
  16. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/changefeed.py +0 -0
  17. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/concurrency.py +0 -0
  18. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/connection_config.py +0 -0
  19. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/connection_manager.py +0 -0
  20. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/constants.py +0 -0
  21. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/decoding.py +0 -0
  22. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/define_parser.py +0 -0
  23. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/edge.py +0 -0
  24. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/embedded.py +0 -0
  25. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/encoding.py +0 -0
  26. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/enum.py +0 -0
  27. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/exceptions.py +0 -0
  28. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/functions.py +0 -0
  29. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/geo.py +0 -0
  30. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/introspection.py +0 -0
  31. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/live.py +0 -0
  32. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/model_ddl.py +0 -0
  33. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/model_generator.py +0 -0
  34. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/py.typed +0 -0
  35. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/q.py +0 -0
  36. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/query_set.py +0 -0
  37. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/routing.py +0 -0
  38. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/schema_state.py +0 -0
  39. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/signals.py +0 -0
  40. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/subquery.py +0 -0
  41. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/surql_literal.py +0 -0
  42. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/testing/__init__.py +0 -0
  43. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/testing/database.py +0 -0
  44. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/testing/fixtures.py +0 -0
  45. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/time_travel.py +0 -0
  46. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/transaction.py +0 -0
  47. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/types.py +0 -0
  48. {surreal_orm_lite-0.34.0 → surreal_orm_lite-0.34.2}/src/surreal_orm_lite/utils.py +0 -0
@@ -5,6 +5,56 @@ 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.34.1] - 2026-10-22
9
+
10
+ Follow-ups to the review of v0.34.0's model table DDL (#296): `define_access()` and
11
+ `define_table()` refuse an auth model whose `SCHEMAFULL` table would leave out a field SIGNUP
12
+ writes, and the README describes three behaviours of v0.34.0 more precisely. No new public API.
13
+
14
+ ### Fixed
15
+
16
+ - **`define_access()` and `define_table()` raise `TypeError` for a signup field the table would
17
+ leave out** (#296). Since v0.34.0, a model whose schema mode is `SCHEMAFULL`
18
+ (`table_type=TableType.USER`, or a declared `schema_mode=SchemaMode.SCHEMAFULL`) gets from
19
+ `define_access()` and `define_table()` a `SCHEMAFULL` table with one `DEFINE FIELD` per column
20
+ `model_dump()` writes, while SIGNUP writes every field of the model. A field declared with
21
+ `Field(exclude=True)` (`password: str = Field(exclude=True)`) therefore had no column: SurrealDB
22
+ 3.x refused every signup ("The record access signup query failed"), and 2.x dropped the value, a
23
+ password hash, so every signin failed. For such an auth model (`AuthenticatedUserMixin`),
24
+ `access_ddl()` and `define_access()` with `with_table=True`, and `table_ddl()` and
25
+ `define_table()`, now raise `TypeError` before any request, naming the model and the fields.
26
+ Remove `exclude=True` and leave the field out where you serialise the model instead
27
+ (`model_dump(exclude={"password"})`): a table defined by hand does not help, since `update()` and
28
+ `upsert()` replace the record with what `model_dump()` writes, which a required column refuses
29
+ and an optional one loses. `define_access(with_table=False)`, a `SCHEMALESS` table and a model
30
+ that is not an auth model (whose excluded field `save()` never writes) behave as before.
31
+
32
+ ### Notes
33
+
34
+ - **A regenerated `SCHEMAFULL` edge model is applied with `define_table()`**: since v0.34.0,
35
+ `generate_models_from_db()` gives a `SCHEMAFULL` relation table with data fields an edge model
36
+ with `schema_mode=SchemaMode.SCHEMAFULL`, for which `define_relation()` raises `SurrealDbError`
37
+ before any request (it defines no field). Call `define_table()`, which applies the same relation
38
+ statement, then the data fields. README sections 31 and 34 say so.
39
+ - **`define_access()` and `define_table()` write different table types for a model that declares
40
+ no `table_type`** and whose schema mode is not `SCHEMAFULL`: `define_access()` writes no `TYPE`,
41
+ which SurrealDB stores as `TYPE ANY`, and `define_table()` writes `TYPE NORMAL`, so a start-up
42
+ that calls both changes the table's type each time. Declare `table_type` to keep one type. The
43
+ schema mode, fields and permissions are the same in any call order; README "User models" and the
44
+ docstrings no longer say that the two converge on everything.
45
+ - **Undeclared keys in `FLEXIBLE` list items on SurrealDB 2.x and the embedded engine**: the items
46
+ of a list `define_table()` writes `FLEXIBLE` (a list of sub-models holding a `dict`, an `Any` or
47
+ an untyped `list`) keep a key the model does not declare, written raw or by another client (the
48
+ ORM never sends one), while a list that is not `FLEXIBLE` drops it. This is a 2.x limitation:
49
+ `FLEXIBLE` there covers the whole item. 3.x keeps such a key in the items of any list of objects,
50
+ `FLEXIBLE` or not, and refuses it at the top level or in a sub-model. README section 35 and the
51
+ "ORM behaviour: SurrealDB 2.x vs 3.x" row on undeclared fields say so.
52
+ - **Enum columns**: the 0.34.0 entry's list of column kinds leaves out one case: an `enum.Flag` or
53
+ `IntFlag` field is written `int`, since Pydantic also accepts any combination of its members, and
54
+ zero. The README "Field types" table, which says so, is now pinned by a test.
55
+ - Server pins unchanged: tested against SurrealDB 3.3.4 and 2.7.0 and on the embedded engine,
56
+ plus 3.1.5 for backward compatibility.
57
+
8
58
  ## [0.34.0] - 2026-10-21
9
59
 
10
60
  Model table DDL: a model declares its table's schema mode, change feed, permissions and comment
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: surreal-orm-lite
3
- Version: 0.34.0
3
+ Version: 0.34.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
@@ -4321,7 +4321,10 @@ class Person(BaseSurrealModel):
4321
4321
  when the schema was read from another connection than `"default"`, then `server_fields` and `extra="allow"`
4322
4322
  (below). A model declares
4323
4323
  `id: str | None = None` first (`id: RecordID | None = None` for a view: a grouped view's ids are arrays), then one
4324
- line per field, in codepoint order of the columns.
4324
+ line per field, in codepoint order of the columns. A `SCHEMAFULL` relation table with data fields gives an edge model
4325
+ with `schema_mode=SchemaMode.SCHEMAFULL` (since v0.34.0): apply it with `define_table()`, which defines those fields,
4326
+ not `define_relation()`, which raises `SurrealDbError` before any request
4327
+ ([section 34](#34-relation-constraints-relation_in-define_relation)).
4325
4328
  - A class is named after its table in PascalCase, without singularisation (`game_tables` → `GameTables`); a name
4326
4329
  that is taken — a Python keyword, a name the module imports, another table's class — gets `Model` appended (`range`
4327
4330
  → `RangeModel`). The table's indexes and events are comments of its class, and so are its comment, change feed
@@ -5168,6 +5171,11 @@ print(await Follows.define_relation())
5168
5171
  other clauses.
5169
5172
  - `define_relation()` raises `SurrealDbError` before any DDL on a materialized view (the `OVERWRITE` would drop its
5170
5173
  query) and on a definition the ORM cannot read back (it could not keep the other clauses).
5174
+ - An edge model that declares `schema_mode=SchemaMode.SCHEMAFULL` and has data fields — the model
5175
+ `generate_models_from_db()` writes for a `SCHEMAFULL` relation table with data fields, since v0.34.0 — is applied
5176
+ with `define_table()`, not `define_relation()`: `define_relation()` defines no field, so it raises `SurrealDbError`
5177
+ before any request, and `define_table()` applies the same relation statement, then the data fields
5178
+ ([section 35](#35-model-table-ddl-define_table-schema_mode-permissions)).
5171
5179
  - SurrealDB rewrites the `in` and `out` field definitions from `IN` and `OUT` (`TYPE record<person>`): an `ASSERT`
5172
5180
  written on them by hand is lost. Declare the tables with `relation_in` and `relation_out` instead.
5173
5181
  - Why not `ALTER TABLE`, which keeps the other clauses too: on SurrealDB 2.x and the embedded engine, a raw
@@ -5667,7 +5675,10 @@ print(edge.stars)
5667
5675
  same schema mode. A model with `table_type=TableType.USER` (the full ORM's key for an auth table) or
5668
5676
  `schema_mode=SchemaMode.SCHEMAFULL` gets from `define_access()` a `SCHEMAFULL` table with one `DEFINE FIELD` per field
5669
5677
  and the access's permissions, then the access: signup and signin work on SurrealDB 2.x and 3.x, `define_table()` is
5670
- optional, and any call order gives the same table. Every other model keeps v0.17.0's `SCHEMALESS` table.
5678
+ optional, and any call order gives the same table. Every other model keeps v0.17.0's `SCHEMALESS` table. One that
5679
+ declares no `table_type` gets no `TYPE` from `define_access()`, which SurrealDB stores as `TYPE ANY`, and `TYPE NORMAL`
5680
+ from `define_table()`: calling both changes the table's type each time, so declare `table_type` to keep one. The schema
5681
+ mode, fields and permissions are the same in any call order.
5671
5682
 
5672
5683
  ```python
5673
5684
  from surreal_orm_lite import AuthenticatedUserMixin, TableType
@@ -5692,6 +5703,13 @@ print(*table, sep="\n")
5692
5703
  declares its own.
5693
5704
  - A `TableType.USER` model with `extra="allow"` raises `TypeError` there (with `with_table=True`), since its table is
5694
5705
  `SCHEMAFULL`; a `server_fields` column without a constant default needs a database `DEFAULT` ("Field types" above).
5706
+ - A field SIGNUP writes that `Field(exclude=True)` keeps out of `model_dump()` (`password: str = Field(exclude=True)`)
5707
+ raises `TypeError` too, naming the field, before any request (with a `SCHEMAFULL` schema mode): `define_access()`
5708
+ with `with_table=True` and `define_table()` both refuse it, since the table would not define it, so SurrealDB 3.x
5709
+ would refuse every signup and 2.x drop the password hash, failing every signin. Remove `exclude=True` and leave the
5710
+ field out where you serialise the model instead (`model_dump(exclude={"password"})`): `update()` and `upsert()`
5711
+ replace the record with what `model_dump()` writes, so even over a table defined by hand (`with_table=False`) they
5712
+ would be refused (a required column) or drop the hash (an optional one), failing every later signin.
5695
5713
  - Upgrading: a 0.33 `TableType.USER` model's table turns `SCHEMAFULL` at its next `define_access()`; records already
5696
5714
  there are checked at their next write.
5697
5715
 
@@ -5703,7 +5721,8 @@ print(*table, sep="\n")
5703
5721
  - **A write naming a field the table does not define** — `merge()` with a key the model does not declare,
5704
5722
  `bulk_update()`, a raw query — is refused on SurrealDB 3.x
5705
5723
  (`Found field 'nope', but no such field exists for table 'note'`) and the field dropped on 2.x and the embedded
5706
- engine, silently: declare the field on the model.
5724
+ engine, silently: declare the field on the model. The items of a list of objects are the exception ("SurrealDB 2.x
5725
+ and 3.x" below).
5707
5726
  - **A node model over a table that holds edges**: `define_table()` refuses a relation table (`TYPE RELATION`), but not
5708
5727
  a `TYPE ANY` table holding edges, whose edges its `TYPE NORMAL` keeps while refusing every new one: declare
5709
5728
  `table_type=TableType.ANY` for a table that holds records and edges.
@@ -5720,7 +5739,10 @@ print(*table, sep="\n")
5720
5739
  of objects from every item — the `dict`, `Any` or untyped `list` of a sub-model in a `list[M]`, at any depth
5721
5740
  (`items.*.meta`) — unless the list is `FLEXIBLE` too, then refuse the record's next write (`Found NONE for field`).
5722
5741
  `define_table()` writes such a list `FLEXIBLE` before 3.x; the item's typed fields are still checked. 3.x keeps those
5723
- values without it.
5742
+ 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
5743
+ declare, written raw or by another client (the ORM never sends one), which they drop from a list that is not
5744
+ `FLEXIBLE`: a 2.x limitation, since `FLEXIBLE` there covers the whole item. 3.x keeps such a key in the items of any
5745
+ list of objects, `FLEXIBLE` or not, and refuses it at the top level or in a sub-model.
5724
5746
  - On SurrealDB 3.x a required untyped array refuses an object item unless its items are defined
5725
5747
  (`no such field exists`): `define_table()` writes the item definition `TYPE any` (`bag.*`) on every line.
5726
5748
  - A write naming a field a `SCHEMAFULL` table does not define is refused on 3.x and dropped on 2.x and the embedded
@@ -6075,7 +6097,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6075
6097
  | 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 |
6076
6098
  | 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 |
6077
6099
  | 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 |
6078
- | 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 | the write is refused (`no such field exists`) | v0.34.0 |
6100
+ | 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 |
6079
6101
  | 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 |
6080
6102
 
6081
6103
  > **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.
@@ -4270,7 +4270,10 @@ class Person(BaseSurrealModel):
4270
4270
  when the schema was read from another connection than `"default"`, then `server_fields` and `extra="allow"`
4271
4271
  (below). A model declares
4272
4272
  `id: str | None = None` first (`id: RecordID | None = None` for a view: a grouped view's ids are arrays), then one
4273
- line per field, in codepoint order of the columns.
4273
+ line per field, in codepoint order of the columns. A `SCHEMAFULL` relation table with data fields gives an edge model
4274
+ with `schema_mode=SchemaMode.SCHEMAFULL` (since v0.34.0): apply it with `define_table()`, which defines those fields,
4275
+ not `define_relation()`, which raises `SurrealDbError` before any request
4276
+ ([section 34](#34-relation-constraints-relation_in-define_relation)).
4274
4277
  - A class is named after its table in PascalCase, without singularisation (`game_tables` → `GameTables`); a name
4275
4278
  that is taken — a Python keyword, a name the module imports, another table's class — gets `Model` appended (`range`
4276
4279
  → `RangeModel`). The table's indexes and events are comments of its class, and so are its comment, change feed
@@ -5117,6 +5120,11 @@ print(await Follows.define_relation())
5117
5120
  other clauses.
5118
5121
  - `define_relation()` raises `SurrealDbError` before any DDL on a materialized view (the `OVERWRITE` would drop its
5119
5122
  query) and on a definition the ORM cannot read back (it could not keep the other clauses).
5123
+ - An edge model that declares `schema_mode=SchemaMode.SCHEMAFULL` and has data fields — the model
5124
+ `generate_models_from_db()` writes for a `SCHEMAFULL` relation table with data fields, since v0.34.0 — is applied
5125
+ with `define_table()`, not `define_relation()`: `define_relation()` defines no field, so it raises `SurrealDbError`
5126
+ before any request, and `define_table()` applies the same relation statement, then the data fields
5127
+ ([section 35](#35-model-table-ddl-define_table-schema_mode-permissions)).
5120
5128
  - SurrealDB rewrites the `in` and `out` field definitions from `IN` and `OUT` (`TYPE record<person>`): an `ASSERT`
5121
5129
  written on them by hand is lost. Declare the tables with `relation_in` and `relation_out` instead.
5122
5130
  - Why not `ALTER TABLE`, which keeps the other clauses too: on SurrealDB 2.x and the embedded engine, a raw
@@ -5616,7 +5624,10 @@ print(edge.stars)
5616
5624
  same schema mode. A model with `table_type=TableType.USER` (the full ORM's key for an auth table) or
5617
5625
  `schema_mode=SchemaMode.SCHEMAFULL` gets from `define_access()` a `SCHEMAFULL` table with one `DEFINE FIELD` per field
5618
5626
  and the access's permissions, then the access: signup and signin work on SurrealDB 2.x and 3.x, `define_table()` is
5619
- optional, and any call order gives the same table. Every other model keeps v0.17.0's `SCHEMALESS` table.
5627
+ optional, and any call order gives the same table. Every other model keeps v0.17.0's `SCHEMALESS` table. One that
5628
+ declares no `table_type` gets no `TYPE` from `define_access()`, which SurrealDB stores as `TYPE ANY`, and `TYPE NORMAL`
5629
+ from `define_table()`: calling both changes the table's type each time, so declare `table_type` to keep one. The schema
5630
+ mode, fields and permissions are the same in any call order.
5620
5631
 
5621
5632
  ```python
5622
5633
  from surreal_orm_lite import AuthenticatedUserMixin, TableType
@@ -5641,6 +5652,13 @@ print(*table, sep="\n")
5641
5652
  declares its own.
5642
5653
  - A `TableType.USER` model with `extra="allow"` raises `TypeError` there (with `with_table=True`), since its table is
5643
5654
  `SCHEMAFULL`; a `server_fields` column without a constant default needs a database `DEFAULT` ("Field types" above).
5655
+ - A field SIGNUP writes that `Field(exclude=True)` keeps out of `model_dump()` (`password: str = Field(exclude=True)`)
5656
+ raises `TypeError` too, naming the field, before any request (with a `SCHEMAFULL` schema mode): `define_access()`
5657
+ with `with_table=True` and `define_table()` both refuse it, since the table would not define it, so SurrealDB 3.x
5658
+ would refuse every signup and 2.x drop the password hash, failing every signin. Remove `exclude=True` and leave the
5659
+ field out where you serialise the model instead (`model_dump(exclude={"password"})`): `update()` and `upsert()`
5660
+ replace the record with what `model_dump()` writes, so even over a table defined by hand (`with_table=False`) they
5661
+ would be refused (a required column) or drop the hash (an optional one), failing every later signin.
5644
5662
  - Upgrading: a 0.33 `TableType.USER` model's table turns `SCHEMAFULL` at its next `define_access()`; records already
5645
5663
  there are checked at their next write.
5646
5664
 
@@ -5652,7 +5670,8 @@ print(*table, sep="\n")
5652
5670
  - **A write naming a field the table does not define** — `merge()` with a key the model does not declare,
5653
5671
  `bulk_update()`, a raw query — is refused on SurrealDB 3.x
5654
5672
  (`Found field 'nope', but no such field exists for table 'note'`) and the field dropped on 2.x and the embedded
5655
- engine, silently: declare the field on the model.
5673
+ engine, silently: declare the field on the model. The items of a list of objects are the exception ("SurrealDB 2.x
5674
+ and 3.x" below).
5656
5675
  - **A node model over a table that holds edges**: `define_table()` refuses a relation table (`TYPE RELATION`), but not
5657
5676
  a `TYPE ANY` table holding edges, whose edges its `TYPE NORMAL` keeps while refusing every new one: declare
5658
5677
  `table_type=TableType.ANY` for a table that holds records and edges.
@@ -5669,7 +5688,10 @@ print(*table, sep="\n")
5669
5688
  of objects from every item — the `dict`, `Any` or untyped `list` of a sub-model in a `list[M]`, at any depth
5670
5689
  (`items.*.meta`) — unless the list is `FLEXIBLE` too, then refuse the record's next write (`Found NONE for field`).
5671
5690
  `define_table()` writes such a list `FLEXIBLE` before 3.x; the item's typed fields are still checked. 3.x keeps those
5672
- values without it.
5691
+ 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
5692
+ declare, written raw or by another client (the ORM never sends one), which they drop from a list that is not
5693
+ `FLEXIBLE`: a 2.x limitation, since `FLEXIBLE` there covers the whole item. 3.x keeps such a key in the items of any
5694
+ list of objects, `FLEXIBLE` or not, and refuses it at the top level or in a sub-model.
5673
5695
  - On SurrealDB 3.x a required untyped array refuses an object item unless its items are defined
5674
5696
  (`no such field exists`): `define_table()` writes the item definition `TYPE any` (`bag.*`) on every line.
5675
5697
  - A write naming a field a `SCHEMAFULL` table does not define is refused on 3.x and dropped on 2.x and the embedded
@@ -6024,7 +6046,7 @@ The SDK's in-process engine has a table of its own: [ORM behaviour: the embedded
6024
6046
  | 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 |
6025
6047
  | 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 |
6026
6048
  | 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 |
6027
- | 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 | the write is refused (`no such field exists`) | v0.34.0 |
6049
+ | 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 |
6028
6050
  | 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 |
6029
6051
 
6030
6052
  > **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.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "surreal-orm-lite"
3
- version = "0.34.0"
3
+ version = "0.34.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.34.0"
1
+ __version__ = "0.34.2"
2
2
 
3
3
  from ._sdk import BoundExcluded, BoundIncluded
4
4
  from .aggregations import Aggregation, Avg, Count, Max, Min, Sum
@@ -265,6 +265,20 @@ class AuthenticatedUserMixin:
265
265
  server_owned = cls.get_server_fields() # type: ignore[attr-defined]
266
266
  return [name for name in cls._model_field_names() if name != "id" and name not in server_owned]
267
267
 
268
+ @classmethod
269
+ def _signup_fields_excluded(cls) -> list[str]:
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`.
278
+ """
279
+ fields = getattr(cls, "model_fields", {}) or {}
280
+ return [name for name in cls._signup_fields() if fields[name].exclude is True]
281
+
268
282
  @classmethod
269
283
  def _signup_primary_key(cls) -> str | None:
270
284
  """The primary key SIGNUP builds the record id from, or ``None`` for a server id.
@@ -344,8 +358,12 @@ class AuthenticatedUserMixin:
344
358
  own), since such a table refuses or empties the record SIGNUP creates unless its fields
345
359
  are defined; every other model gets v0.17.0's ``SCHEMALESS`` table statement, with the
346
360
  comment, change feed, permissions and ``TYPE`` clause it declares — v0.17.0's text when it
347
- declares none. Any call order with ``define_table()`` converges: both write the same
348
- schema mode and fields.
361
+ declares none. Any call order with ``define_table()`` converges on the schema mode, the
362
+ fields and the permissions. Not on the table type of a model that declares no
363
+ ``table_type`` and whose schema mode is not ``SCHEMAFULL`` (neither key, say): this
364
+ statement writes no ``TYPE``, which SurrealDB stores as ``TYPE ANY``, while
365
+ ``define_table()`` writes ``TYPE NORMAL``, so calling both changes the type each time.
366
+ Declaring ``table_type`` gives both the same type.
349
367
 
350
368
  Args:
351
369
  overwrite: ``True`` (default) emits ``OVERWRITE`` — the model is the source of
@@ -365,7 +383,15 @@ class AuthenticatedUserMixin:
365
383
  TypeError: with ``with_table=True``, a ``TableType.USER`` model with
366
384
  ``extra="allow"`` (its ``SCHEMAFULL`` table refuses or drops the fields the
367
385
  model does not declare), or, for a model whose schema mode is ``SCHEMAFULL``,
368
- an *overwrite* that is not a ``bool``.
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.
391
+ 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.
369
395
  ValueError: an auth key naming no field of the model, an unknown hashing family or
370
396
  duration, a primary key SIGNUP cannot bind; with ``with_table=True`` and a
371
397
  ``SCHEMAFULL`` schema mode, a ``FLEXIBLE`` request on a kind without an object
@@ -390,6 +416,7 @@ class AuthenticatedUserMixin:
390
416
 
391
417
  statements: list[str] = []
392
418
  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.
393
420
  table_ddl: list[str] = cls._table_ddl(overwrite, cls._access_table_clauses()) # type: ignore[attr-defined]
394
421
  statements.extend(table_ddl)
395
422
  elif with_table:
@@ -468,7 +495,11 @@ class AuthenticatedUserMixin:
468
495
  statement. Every other model applies v0.17.0's two statements, whose ``OVERWRITE``
469
496
  resets the table clauses it does not write; when it declares ``permissions``, the table
470
497
  statement is applied on its own first, and a refusal names the table, never the rules.
471
- Any call order with ``define_table()`` converges, so neither needs the other first.
498
+ Any call order with ``define_table()`` converges on the schema mode, the fields and the
499
+ permissions, so neither needs the other first. A model that declares no ``table_type``
500
+ and whose schema mode is not ``SCHEMAFULL`` gets no ``TYPE`` here (SurrealDB stores
501
+ ``TYPE ANY``) and ``TYPE NORMAL`` from ``define_table()``, so calling both changes the
502
+ table's type each time; declaring ``table_type`` keeps one type.
472
503
 
473
504
  Args:
474
505
  overwrite: see :meth:`access_ddl`.
@@ -484,7 +515,9 @@ class AuthenticatedUserMixin:
484
515
  access statement.
485
516
 
486
517
  Raises:
487
- TypeError: see :meth:`access_ddl` — before any request.
518
+ 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.
488
521
  ValueError: see :meth:`access_ddl` — before any request.
489
522
  SurrealDbError: if the server rejects a statement. The most likely cause on
490
523
  **SurrealDB 2.6.x** is ``with_refresh=True``: that line cannot parse
@@ -47,6 +47,7 @@ from .schema import (
47
47
  read_table_fields,
48
48
  remove_event_statement,
49
49
  remove_view_statement,
50
+ signup_excluded_refused,
50
51
  table_holds_records,
51
52
  table_kind,
52
53
  table_permissions_text,
@@ -1493,7 +1494,9 @@ class BaseSurrealModel(BaseModel):
1493
1494
  ``FLEXIBLE`` following *clauses*' schema mode. Pure.
1494
1495
 
1495
1496
  Raises:
1496
- TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"``.
1497
+ TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"`` (UX1); then, when
1498
+ *clauses* are ``SCHEMAFULL``, an auth model (``AuthenticatedUserMixin``) with a field SIGNUP writes that
1499
+ ``Field(exclude=True)`` keeps out of the columns (SG1, #296).
1497
1500
  ValueError: see :meth:`_table_columns`.
1498
1501
  """
1499
1502
  if cls._is_view():
@@ -1504,6 +1507,16 @@ class BaseSurrealModel(BaseModel):
1504
1507
  raise TypeError(user_extra_refused(cls.__name__))
1505
1508
  schemafull = clauses.schema_mode == SchemaMode.SCHEMAFULL.value
1506
1509
  fields = [field_statement(table, column, overwrite=overwrite, schemafull=schemafull) for column in cls._table_columns()]
1510
+ if schemafull:
1511
+ # Imported here, only for a SCHEMAFULL table: model_auth sits above this module.
1512
+ from .model_auth import AuthenticatedUserMixin
1513
+
1514
+ # SG1 (#296), after the other refusals: SIGNUP writes a field the columns leave out, which the SCHEMAFULL
1515
+ # table would refuse (3.x) or drop (2.x) — checked here, as UX1 is, so that table_ddl(), define_table(),
1516
+ # access_ddl() and define_access() refuse the same model.
1517
+ excluded: list[str] = cls._signup_fields_excluded() if issubclass(cls, AuthenticatedUserMixin) else []
1518
+ if excluded:
1519
+ raise TypeError(signup_excluded_refused(cls.__name__, excluded))
1507
1520
  return [statement, *fields]
1508
1521
 
1509
1522
  @classmethod
@@ -1546,7 +1559,12 @@ class BaseSurrealModel(BaseModel):
1546
1559
 
1547
1560
  Raises:
1548
1561
  TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"`` (its table is
1549
- ``SCHEMAFULL``, which refuses or drops the fields the model does not declare).
1562
+ ``SCHEMAFULL``, which refuses or drops the fields the model does not declare); an auth model
1563
+ (``AuthenticatedUserMixin``) whose declared schema mode is ``SCHEMAFULL`` with a field SIGNUP writes
1564
+ that ``Field(exclude=True)`` keeps out of the columns (``password: str = Field(exclude=True)``), which
1565
+ the table would refuse or drop, failing every signup or signin — ``define_access()`` refuses it too;
1566
+ remove ``exclude=True`` (a table defined by hand would not help: ``update()`` and ``upsert()`` replace
1567
+ the record with what ``model_dump()`` writes).
1550
1568
  ValueError: a ``FLEXIBLE`` request on a kind without an object; a computed field's expression that is not
1551
1569
  SurrealQL the ORM can embed.
1552
1570
 
@@ -1573,7 +1591,7 @@ class BaseSurrealModel(BaseModel):
1573
1591
  ``SCHEMAFULL`` (its own). *method* names the caller in the transaction check, TG1–TG4 and RG1–RG3.
1574
1592
 
1575
1593
  In order, each step before any later request: the transaction check; a view model applies
1576
- :meth:`define_view` (its own guards) and returns its statement; the pure statements (E1, UX1, FX1, CV1);
1594
+ :meth:`define_view` (its own guards) and returns its statement; the pure statements (E1, UX1, FX1, CV1, SG1);
1577
1595
  ``INFO FOR DB`` and the table guard (:meth:`_guarded_table_statement`) — for a node model TG1 a view, TG2 a
1578
1596
  definition the parser cannot read, TG3 a relation table, and, for a table that exists with ``overwrite=True``,
1579
1597
  TG2 a carried table clause that fails its check; for an edge model ``define_relation()``'s RG1–RG3 —; for a
@@ -1722,8 +1740,9 @@ class BaseSurrealModel(BaseModel):
1722
1740
  The statements applied (or queued), table first, joined with ``"\\n"``.
1723
1741
 
1724
1742
  Raises:
1725
- TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"`` — before any
1726
- request.
1743
+ TypeError: *overwrite* is not a ``bool``; a ``TableType.USER`` model with ``extra="allow"``; an auth model
1744
+ whose declared schema mode is ``SCHEMAFULL`` with a field SIGNUP writes that ``Field(exclude=True)``
1745
+ keeps out of the columns (see :meth:`table_ddl`) — before any request.
1727
1746
  ValueError: a ``FLEXIBLE`` request on a kind without an object; a computed field's expression that is not
1728
1747
  SurrealQL the ORM can embed — before any request.
1729
1748
  SurrealDbError: *tx* belongs to another connection — before any request; the table is a materialized view
@@ -85,6 +85,8 @@ 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
90
 
89
91
  The module sits low in the package: it imports neither ``model_base`` nor ``connection_manager``, so both can import
90
92
  it.
@@ -1944,3 +1946,19 @@ def user_extra_refused(model: str) -> str:
1944
1946
  f'{model}: extra="allow" keeps fields the model does not declare, and a TableType.USER table is SCHEMAFULL, which '
1945
1947
  f'refuses them (SurrealDB 3.x) or drops them (2.x); remove extra="allow" from {model}.'
1946
1948
  )
1949
+
1950
+
1951
+ 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."""
1955
+ names = ", ".join(repr(name) for name in fields)
1956
+ pronoun = "it" if len(fields) == 1 else "them"
1957
+ 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}."
1964
+ )