surreal-orm-lite 0.34.2__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.34.2 → surreal_orm_lite-0.35.2}/CHANGELOG.md +192 -0
  2. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/PKG-INFO +373 -51
  3. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/README.md +372 -50
  4. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/pyproject.toml +1 -1
  5. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/__init__.py +8 -1
  6. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/connection_manager.py +105 -0
  7. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_auth.py +49 -19
  8. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_base.py +293 -26
  9. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/query_set.py +329 -72
  10. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/schema.py +17 -11
  11. surreal_orm_lite-0.35.2/src/surreal_orm_lite/search.py +1390 -0
  12. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/subquery.py +4 -0
  13. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/transaction.py +18 -5
  14. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/.gitignore +0 -0
  15. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/LICENSE +0 -0
  16. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/Makefile +0 -0
  17. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/_sdk.py +0 -0
  18. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/aggregations.py +0 -0
  19. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/auth.py +0 -0
  20. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/cache.py +0 -0
  21. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/changefeed.py +0 -0
  22. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/concurrency.py +0 -0
  23. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/connection_config.py +0 -0
  24. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/constants.py +0 -0
  25. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/decoding.py +0 -0
  26. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/define_parser.py +0 -0
  27. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/edge.py +0 -0
  28. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/embedded.py +0 -0
  29. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/encoding.py +0 -0
  30. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/enum.py +0 -0
  31. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/exceptions.py +0 -0
  32. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/functions.py +0 -0
  33. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/geo.py +0 -0
  34. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/introspection.py +0 -0
  35. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/live.py +0 -0
  36. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_ddl.py +0 -0
  37. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/model_generator.py +0 -0
  38. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/py.typed +0 -0
  39. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/q.py +0 -0
  40. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/routing.py +0 -0
  41. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/schema_state.py +0 -0
  42. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/signals.py +0 -0
  43. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/surql_literal.py +0 -0
  44. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/testing/__init__.py +0 -0
  45. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/testing/database.py +0 -0
  46. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/testing/fixtures.py +0 -0
  47. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/time_travel.py +0 -0
  48. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/types.py +0 -0
  49. {surreal_orm_lite-0.34.2 → surreal_orm_lite-0.35.2}/src/surreal_orm_lite/utils.py +0 -0
@@ -5,6 +5,198 @@ 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
+
56
+ ## [0.35.0] - 2026-10-22
57
+
58
+ Full-text search: a queryset searches text with the full SurrealDB-ORM's `search()`, and scores and
59
+ highlights the matches with `SearchScore` and `SearchHighlight` (plus `SearchOffsets`), over the
60
+ analyzers and full-text indexes a model declares with `Analyzer` and `Index` and applies with
61
+ `define_indexes()`. SurrealDB tokenises, matches, scores (BM25) and highlights on the server; the
62
+ ORM binds every search text and highlight tag as a query variable. The same code runs on SurrealDB
63
+ 2.x, 3.x and the embedded engine, each line getting its own index keyword. The first minor of Tier
64
+ 3's search phase.
65
+
66
+ ### Added
67
+
68
+ - **`QuerySet.search(**field_queries)`**, the full SurrealDB-ORM's method: keeps the records whose
69
+ fields match. Each keyword names a field — a Python field name (an aliased field searches its
70
+ column) or a dotted path — and the text to match, bound, never written into the statement. The
71
+ terms of one text are AND-ed, and so are the fields; each field gets the next match reference, in
72
+ call order and across calls. `search()` applies wherever `filter()` does, AND-ed after
73
+ `filter()`, `Q` and `nearby()`: `exec()`, `first()`, `get()` without an id, `count()`,
74
+ `exists()`, the aggregations, `values()` groups, `bulk_update()`, `bulk_delete()`, `patch()` and a
75
+ `Subquery`, whose references start again at 0 (the full ORM applies it to `exec()` only).
76
+ `get(<id>)` and `all()` ignore it, as they ignore `filter()`.
77
+ - **`SearchScore(ref)` and `SearchHighlight(open_tag, close_tag, ref)`**, the full ORM's search
78
+ annotations, **and `SearchOffsets(ref)`**, an addition (SurrealDB's `search::offsets()`), in
79
+ `annotate()`: the BM25 score (a `float`), the text with each matched term between the tags (bound
80
+ as query variables), and where the matched terms sit. `exec()`, `first()` and `get()` without an
81
+ id set each value as an attribute named after its alias, never written back (`model_dump()`,
82
+ `save()` and `update()` leave it out); `order_by("-relevance")` sorts on an alias. A reference no
83
+ `search()` gave, or an alias that would shadow a field, its column or an attribute of the model,
84
+ raises `ValueError` before any request.
85
+ - **`Analyzer` and `Index`**, with the argument names of the full ORM's `DefineAnalyzer` and
86
+ `CreateIndex`: an analyzer's name, tokenizers (`blank`, `camel`, `class`, `punct`), filters
87
+ (`ascii`, `lowercase`, `uppercase`, `ngram`, `edgengram`, `snowball`, `mapper`), `function=` and
88
+ `comment=`; a plain index, a `UNIQUE` one (`unique=True`) or a full-text one (`search_analyzer=`,
89
+ an `Analyzer` or the name of an analyzer the database defines), which covers one field and takes
90
+ `bm25=` (`True`, or a `(k1, b)` pair) and `highlights=True`. Every argument is checked at
91
+ construction, more strictly than the server where its answer would be silently wrong.
92
+ - **`SurrealConfigDict(indexes=[Index(...)])`** declares a model's indexes, checked when the class
93
+ is defined (distinct names, one full-text index per column, one `Analyzer` per analyzer name);
94
+ `get_indexes()` returns them in declaration order, and `Model.index_ddl()` renders their
95
+ `DEFINE ANALYZER` and `DEFINE INDEX` statements without a connection, for SurrealDB 3.x or, with
96
+ `line=2`, for 2.x.
97
+ - **`await Model.define_indexes()`** applies them on the model's connection: it reads what the
98
+ database holds (`INFO FOR DB`, `INFO FOR TABLE`) and, when a full-text index is declared, the
99
+ server line (`client.version()`), then defines what is missing and redefines what changed, one
100
+ statement per request, and returns the statements applied — `[]` on an unchanged database, so it
101
+ is safe at start-up. `overwrite=False` defines only what is missing (`IF NOT EXISTS`); `tx=` joins
102
+ a transaction. Nothing is removed: **`await Model.remove_index(name)`** removes an index by name
103
+ (`REMOVE INDEX IF EXISTS`; `missing_ok=False` raises `SurrealDbNotFoundError` for a missing one).
104
+ - **`SurrealDBConnectionManager.define_analyzer(analyzer)` and `remove_analyzer(analyzer)`**, for
105
+ the analyzers, which belong to the database rather than to a table: `define_analyzer()` defines
106
+ one on the current connection, redefining it only when it changed, and returns the statements
107
+ applied; `remove_analyzer()` removes one by name and refuses one an index uses.
108
+ - README section 36, "Full-text search (`search()`, `SearchScore`, `SearchHighlight`, `Analyzer`,
109
+ `Index`)" (declaring and applying, searching, scoring and highlighting, what SurrealDB decides,
110
+ changing an analyzer, transactions, live queries, what is not supported, a note for users of the
111
+ full SurrealDB-ORM), seven rows of the "ORM behaviour: SurrealDB 2.x vs 3.x" table and one of the
112
+ embedded engine's, and the example notebook `examples/v0.35.0_full_text_search.ipynb`.
113
+
114
+ ```python
115
+ ENGLISH = Analyzer("english", ["class", "blank"], ["lowercase", "ascii", "snowball(english)"])
116
+
117
+
118
+ class Article(BaseSurrealModel):
119
+ model_config = SurrealConfigDict(
120
+ table_name="article",
121
+ indexes=[Index("article_title", "title", search_analyzer=ENGLISH, bm25=True, highlights=True)],
122
+ )
123
+
124
+ id: str | None = None
125
+ title: str
126
+
127
+
128
+ await Article.define_table()
129
+ await Article.define_indexes() # FULLTEXT on SurrealDB 3.x, SEARCH on 2.x; [] when nothing changed
130
+ articles = await (
131
+ Article.objects()
132
+ .search(title="physics")
133
+ .annotate(relevance=SearchScore(0), snippet=SearchHighlight("<b>", "</b>", 0))
134
+ .order_by("-relevance")
135
+ .exec()
136
+ )
137
+ # each article carries .relevance (a float) and .snippet (its title, "<b>physics</b>" highlighted)
138
+ ```
139
+
140
+ ### Fixed
141
+
142
+ - **A transaction error no longer quotes a bound search text**: a failed statement whose reason
143
+ reports a missing full-text index ("no suitable index supporting the expression") is cut after
144
+ the expression's operator (`… the expression: slug @0@`), since SurrealDB 3.x quotes the bound
145
+ value after it. The server's text stays reachable as the error's suppressed `__context__`, as
146
+ for a reason that quotes the query.
147
+
148
+ ### Notes
149
+
150
+ - **The index keyword follows the server line**, read from `client.version()`: `FULLTEXT` on
151
+ SurrealDB 3.x, `SEARCH` on 2.x and the embedded engine, each of which refuses the other's. The ORM
152
+ writes one field per full-text index on both lines.
153
+ - **Only what changed is redefined**: an unchanged index is never redefined, so start-up never
154
+ rebuilds. A rebuild re-reads the table: seconds per thousands of records on SurrealDB 2.x and the
155
+ embedded engine.
156
+ - **A changed analyzer rebuilds its indexes**: SurrealDB keeps the tokens of the records an index
157
+ holds when their analyzer changes, so a search would miss them. When `define_indexes()` or
158
+ `define_analyzer()` redefines an analyzer, it rebuilds every index of the database that uses it
159
+ (`REBUILD INDEX`) and logs one warning naming them (logger `surreal_orm_lite.search`). In a
160
+ transaction, `define_indexes(tx=…)` refuses an analyzer to define or redefine, and
161
+ `define_analyzer(tx=…)` a redefinition with indexes to rebuild, before any DDL: call
162
+ `define_analyzer()` without `tx=` first.
163
+ - **`remove_analyzer()` is safe on SurrealDB 2.x**: 2.x and the embedded engine accept removing an
164
+ analyzer an index uses, then refuse every write to the table; the ORM refuses it first on every
165
+ line, with SurrealDB 3.x's own message.
166
+ - **A live query's searched subquery is checked**: before a live query whose `Subquery` searches,
167
+ the ORM checks that each searched field has a full-text index and raises `SurrealDbError` naming
168
+ the field otherwise, before the live query starts.
169
+ - **BM25 scores depend on the line**: a term found in more than half the documents scores `0.0` on
170
+ SurrealDB 3.x and below zero on 2.x and the embedded engine, where a text holding such a term
171
+ therefore scores lower than without it. On 2.x and the embedded engine, BM25 statistics count an
172
+ index's records again at each rebuild or redefinition and keep counting deleted records, so the
173
+ scores depend on the index's history (the matches do not): a documented 2.x limitation. SurrealDB
174
+ 3.x keeps them exact; compare scores within one query, and upgrade to 3.x where scores must not
175
+ depend on the index's history.
176
+ - **Refused before any request** (`SurrealDbError`): `search()` with `as_of()`, in either order and
177
+ in a subquery under it (the index holds the records' current text); with `live()` and `watch()`,
178
+ since SurrealDB accepts a live query whose condition holds a full-text match but never notifies
179
+ it — search in a `Subquery` instead, which notifies the records created or updated to match; and
180
+ with `changes()`. A search on a field without a full-text index raises `SurrealDbError` naming the
181
+ field instead of the server's error, which on SurrealDB 3.x quotes the search text, in a
182
+ transaction too. **Indexes on materialized views** are refused (`ValueError` when the class is
183
+ defined): SurrealDB 3.x does not index the records a view gains after the index is defined.
184
+ - **Call `define_table()` before `define_indexes()`**: on a table that does not exist yet,
185
+ SurrealDB's `DEFINE INDEX` creates it `TYPE ANY SCHEMALESS PERMISSIONS NONE`, which
186
+ `define_table()` replaces but `define_table(overwrite=False)` leaves as it is.
187
+ - **SurrealDB 3.1** (tested with 3.1.5 for backward compatibility): two `search()` fields AND-ed
188
+ can also return records that match only one of them, scored `0.0` on the other; a field without a
189
+ full-text index answers no record instead of an error; and a live query whose subquery searches
190
+ such a field starts.
191
+ - **Deferred**: `search()` with `as_of()` on a versioned SurrealDB 3.x server; indexes on
192
+ materialized views once SurrealDB indexes the records a view gains; 3.x's boolean match operators
193
+ (`@AND@`/`@OR@`) and `highlight_all`; `indexes=[Index(...)]` in generated models;
194
+ `define_index()`/`remove_index()` on the connection manager for tables without a model;
195
+ `Index(concurrently=True)`. Vector indexes come in v0.36.0, `hybrid_search()` in v0.37.0, and
196
+ analyzers and indexes in migrations in v0.38.0.
197
+ - Server pins unchanged: tested against SurrealDB 3.3.4 and 2.7.0 and on the embedded engine,
198
+ plus 3.1.5 for backward compatibility.
199
+
8
200
  ## [0.34.1] - 2026-10-22
9
201
 
10
202
  Follow-ups to the review of v0.34.0's model table DDL (#296): `define_access()` and