brainlessdb 0.7.0__tar.gz → 0.7.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.
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/CHANGELOG.md +14 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/PKG-INFO +20 -20
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/README.md +19 -19
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/brainlessdb/__init__.py +3 -3
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/brainlessdb/collection.py +3 -3
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/brainlessdb/struct.py +3 -3
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/justfile +1 -1
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/pyproject.toml +1 -1
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/test_results.txt +40 -40
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/conftest.py +6 -6
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_collection.py +6 -6
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_deserialization.py +15 -15
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_fields.py +6 -6
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_load_errors.py +4 -4
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/uv.lock +1 -1
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/.gitignore +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/.python-version +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/brainlessdb/bucket.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/brainlessdb/client.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/brainlessdb/fields.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/cliff.toml +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/code_style_guide.md +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/__init__.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/mock_nats.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_client.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_sync.py +0 -0
- {brainlessdb-0.7.0 → brainlessdb-0.7.2}/tests/test_thread_safety.py +0 -0
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## [0.7.2] - 2026-02-08
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
- Commit pyproject.toml and brainlessdb/__init__.py on just release
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [0.7.1] - 2026-02-08
|
|
14
|
+
|
|
15
|
+
### Fchore
|
|
16
|
+
|
|
17
|
+
- Renamed BrainlessStruct to BrainlessBucket and NestedStruct to BrainlessStruct
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
## [0.7.0] - 2026-02-06
|
|
7
21
|
|
|
8
22
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brainlessdb
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Typed collections backed by NATS JetStream KV
|
|
5
5
|
Author-email: "INSOFT s.r.o." <helpdesk@insoft.cz>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,9 +18,9 @@ Typed collections backed by NATS JetStream KV. In-memory with background sync.
|
|
|
18
18
|
```python
|
|
19
19
|
from typing import Annotated
|
|
20
20
|
from msgspec import Meta
|
|
21
|
-
from brainlessdb import BrainlessDB, BrainlessDBFeat,
|
|
21
|
+
from brainlessdb import BrainlessDB, BrainlessDBFeat, BrainlessBucket
|
|
22
22
|
|
|
23
|
-
class UserV1(
|
|
23
|
+
class UserV1(BrainlessBucket):
|
|
24
24
|
id: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX})]
|
|
25
25
|
name: Annotated[str, Meta()] = ""
|
|
26
26
|
|
|
@@ -46,24 +46,24 @@ await db.stop()
|
|
|
46
46
|
|
|
47
47
|
| Class | Use for | Has UUID | Stored in |
|
|
48
48
|
|-------|---------|----------|-----------|
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
49
|
+
| `BrainlessBucket` | Main entities (User, Order, etc.) | Yes | Own bucket(s) |
|
|
50
|
+
| `BrainlessStruct` | Nested data, app-local data | No | Inside parent entity |
|
|
51
51
|
|
|
52
52
|
```python
|
|
53
|
-
from brainlessdb import
|
|
53
|
+
from brainlessdb import BrainlessBucket, BrainlessStruct
|
|
54
54
|
|
|
55
|
-
# App-local data -
|
|
56
|
-
class UcsLocal(
|
|
55
|
+
# App-local data - BrainlessStruct (no UUID, not an entity)
|
|
56
|
+
class UcsLocal(BrainlessStruct):
|
|
57
57
|
sid: int = 0
|
|
58
58
|
pointer: int = 0
|
|
59
59
|
|
|
60
|
-
# Nested data -
|
|
61
|
-
class Address(
|
|
60
|
+
# Nested data - BrainlessStruct
|
|
61
|
+
class Address(BrainlessStruct):
|
|
62
62
|
street: str = ""
|
|
63
63
|
city: str = ""
|
|
64
64
|
|
|
65
|
-
# Main entity -
|
|
66
|
-
class UserV1(
|
|
65
|
+
# Main entity - BrainlessBucket (has UUID, stored in NATS)
|
|
66
|
+
class UserV1(BrainlessBucket):
|
|
67
67
|
id: Annotated[int, Meta()]
|
|
68
68
|
address: Optional[Address] = None # nested struct
|
|
69
69
|
_: Optional[UcsLocal] = None # app-local struct
|
|
@@ -87,7 +87,7 @@ await brainlessdb.stop()
|
|
|
87
87
|
Persistent data synced across all instances:
|
|
88
88
|
|
|
89
89
|
```python
|
|
90
|
-
class UserV1(
|
|
90
|
+
class UserV1(BrainlessBucket):
|
|
91
91
|
id: Annotated[int, Meta()]
|
|
92
92
|
name: Annotated[str, Meta()] = ""
|
|
93
93
|
```
|
|
@@ -96,7 +96,7 @@ class UserV1(BrainlessStruct):
|
|
|
96
96
|
Ephemeral data (separate bucket, faster sync):
|
|
97
97
|
|
|
98
98
|
```python
|
|
99
|
-
class UserV1(
|
|
99
|
+
class UserV1(BrainlessBucket):
|
|
100
100
|
id: Annotated[int, Meta()]
|
|
101
101
|
status: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.STATE})] = 0
|
|
102
102
|
```
|
|
@@ -105,7 +105,7 @@ class UserV1(BrainlessStruct):
|
|
|
105
105
|
Fast O(1) lookups:
|
|
106
106
|
|
|
107
107
|
```python
|
|
108
|
-
class UserV1(
|
|
108
|
+
class UserV1(BrainlessBucket):
|
|
109
109
|
id: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX})]
|
|
110
110
|
```
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@ class UserV1(BrainlessStruct):
|
|
|
113
113
|
Enforces uniqueness constraint (also auto-indexed):
|
|
114
114
|
|
|
115
115
|
```python
|
|
116
|
-
class UserV1(
|
|
116
|
+
class UserV1(BrainlessBucket):
|
|
117
117
|
email: Annotated[Optional[str], Meta(extra={"brainlessdb_flags": BrainlessDBFeat.UNIQUE})] = None
|
|
118
118
|
```
|
|
119
119
|
|
|
@@ -127,16 +127,16 @@ counter: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX |
|
|
|
127
127
|
Data private to each namespace:
|
|
128
128
|
|
|
129
129
|
```python
|
|
130
|
-
from brainlessdb import
|
|
130
|
+
from brainlessdb import BrainlessBucket, BrainlessStruct
|
|
131
131
|
|
|
132
|
-
class UcsLocal(
|
|
132
|
+
class UcsLocal(BrainlessStruct):
|
|
133
133
|
sid: int = 0
|
|
134
134
|
|
|
135
|
-
class AriLocal(
|
|
135
|
+
class AriLocal(BrainlessStruct):
|
|
136
136
|
channel_id: str = ""
|
|
137
137
|
|
|
138
138
|
# Entity with app-local field
|
|
139
|
-
class UserV1(
|
|
139
|
+
class UserV1(BrainlessBucket):
|
|
140
140
|
id: Annotated[int, Meta()]
|
|
141
141
|
_: Union[UcsLocal, AriLocal, None] = None
|
|
142
142
|
```
|
|
@@ -7,9 +7,9 @@ Typed collections backed by NATS JetStream KV. In-memory with background sync.
|
|
|
7
7
|
```python
|
|
8
8
|
from typing import Annotated
|
|
9
9
|
from msgspec import Meta
|
|
10
|
-
from brainlessdb import BrainlessDB, BrainlessDBFeat,
|
|
10
|
+
from brainlessdb import BrainlessDB, BrainlessDBFeat, BrainlessBucket
|
|
11
11
|
|
|
12
|
-
class UserV1(
|
|
12
|
+
class UserV1(BrainlessBucket):
|
|
13
13
|
id: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX})]
|
|
14
14
|
name: Annotated[str, Meta()] = ""
|
|
15
15
|
|
|
@@ -35,24 +35,24 @@ await db.stop()
|
|
|
35
35
|
|
|
36
36
|
| Class | Use for | Has UUID | Stored in |
|
|
37
37
|
|-------|---------|----------|-----------|
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
38
|
+
| `BrainlessBucket` | Main entities (User, Order, etc.) | Yes | Own bucket(s) |
|
|
39
|
+
| `BrainlessStruct` | Nested data, app-local data | No | Inside parent entity |
|
|
40
40
|
|
|
41
41
|
```python
|
|
42
|
-
from brainlessdb import
|
|
42
|
+
from brainlessdb import BrainlessBucket, BrainlessStruct
|
|
43
43
|
|
|
44
|
-
# App-local data -
|
|
45
|
-
class UcsLocal(
|
|
44
|
+
# App-local data - BrainlessStruct (no UUID, not an entity)
|
|
45
|
+
class UcsLocal(BrainlessStruct):
|
|
46
46
|
sid: int = 0
|
|
47
47
|
pointer: int = 0
|
|
48
48
|
|
|
49
|
-
# Nested data -
|
|
50
|
-
class Address(
|
|
49
|
+
# Nested data - BrainlessStruct
|
|
50
|
+
class Address(BrainlessStruct):
|
|
51
51
|
street: str = ""
|
|
52
52
|
city: str = ""
|
|
53
53
|
|
|
54
|
-
# Main entity -
|
|
55
|
-
class UserV1(
|
|
54
|
+
# Main entity - BrainlessBucket (has UUID, stored in NATS)
|
|
55
|
+
class UserV1(BrainlessBucket):
|
|
56
56
|
id: Annotated[int, Meta()]
|
|
57
57
|
address: Optional[Address] = None # nested struct
|
|
58
58
|
_: Optional[UcsLocal] = None # app-local struct
|
|
@@ -76,7 +76,7 @@ await brainlessdb.stop()
|
|
|
76
76
|
Persistent data synced across all instances:
|
|
77
77
|
|
|
78
78
|
```python
|
|
79
|
-
class UserV1(
|
|
79
|
+
class UserV1(BrainlessBucket):
|
|
80
80
|
id: Annotated[int, Meta()]
|
|
81
81
|
name: Annotated[str, Meta()] = ""
|
|
82
82
|
```
|
|
@@ -85,7 +85,7 @@ class UserV1(BrainlessStruct):
|
|
|
85
85
|
Ephemeral data (separate bucket, faster sync):
|
|
86
86
|
|
|
87
87
|
```python
|
|
88
|
-
class UserV1(
|
|
88
|
+
class UserV1(BrainlessBucket):
|
|
89
89
|
id: Annotated[int, Meta()]
|
|
90
90
|
status: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.STATE})] = 0
|
|
91
91
|
```
|
|
@@ -94,7 +94,7 @@ class UserV1(BrainlessStruct):
|
|
|
94
94
|
Fast O(1) lookups:
|
|
95
95
|
|
|
96
96
|
```python
|
|
97
|
-
class UserV1(
|
|
97
|
+
class UserV1(BrainlessBucket):
|
|
98
98
|
id: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX})]
|
|
99
99
|
```
|
|
100
100
|
|
|
@@ -102,7 +102,7 @@ class UserV1(BrainlessStruct):
|
|
|
102
102
|
Enforces uniqueness constraint (also auto-indexed):
|
|
103
103
|
|
|
104
104
|
```python
|
|
105
|
-
class UserV1(
|
|
105
|
+
class UserV1(BrainlessBucket):
|
|
106
106
|
email: Annotated[Optional[str], Meta(extra={"brainlessdb_flags": BrainlessDBFeat.UNIQUE})] = None
|
|
107
107
|
```
|
|
108
108
|
|
|
@@ -116,16 +116,16 @@ counter: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX |
|
|
|
116
116
|
Data private to each namespace:
|
|
117
117
|
|
|
118
118
|
```python
|
|
119
|
-
from brainlessdb import
|
|
119
|
+
from brainlessdb import BrainlessBucket, BrainlessStruct
|
|
120
120
|
|
|
121
|
-
class UcsLocal(
|
|
121
|
+
class UcsLocal(BrainlessStruct):
|
|
122
122
|
sid: int = 0
|
|
123
123
|
|
|
124
|
-
class AriLocal(
|
|
124
|
+
class AriLocal(BrainlessStruct):
|
|
125
125
|
channel_id: str = ""
|
|
126
126
|
|
|
127
127
|
# Entity with app-local field
|
|
128
|
-
class UserV1(
|
|
128
|
+
class UserV1(BrainlessBucket):
|
|
129
129
|
id: Annotated[int, Meta()]
|
|
130
130
|
_: Union[UcsLocal, AriLocal, None] = None
|
|
131
131
|
```
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"""Brainless DB - Typed collections backed by NATS JetStream KV."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.7.
|
|
3
|
+
__version__ = "0.7.2"
|
|
4
4
|
|
|
5
5
|
from typing import Any, Optional, TypeVar
|
|
6
6
|
|
|
7
7
|
from .client import BrainlessDB
|
|
8
8
|
from .collection import Collection, HasUUID
|
|
9
|
-
from .struct import BrainlessDBFeat,
|
|
9
|
+
from .struct import BrainlessDBFeat, BrainlessBucket, BrainlessStruct, UniqueConstraintError
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"__version__",
|
|
13
13
|
"BrainlessDB",
|
|
14
14
|
"BrainlessDBFeat",
|
|
15
|
+
"BrainlessBucket",
|
|
15
16
|
"BrainlessStruct",
|
|
16
|
-
"NestedStruct",
|
|
17
17
|
"Collection",
|
|
18
18
|
"HasUUID",
|
|
19
19
|
"UniqueConstraintError",
|
|
@@ -11,7 +11,7 @@ import msgspec
|
|
|
11
11
|
from .bucket import Bucket
|
|
12
12
|
from .fields import analyze_fields
|
|
13
13
|
from .struct import (
|
|
14
|
-
|
|
14
|
+
BrainlessBucket,
|
|
15
15
|
ConfigWrapper,
|
|
16
16
|
UniqueConstraintError,
|
|
17
17
|
_register,
|
|
@@ -45,8 +45,8 @@ class Collection(Generic[T]):
|
|
|
45
45
|
"""Collection of structs backed by NATS KV buckets."""
|
|
46
46
|
|
|
47
47
|
def __init__(self, client: "BrainlessDB", cls: type[T]) -> None:
|
|
48
|
-
if
|
|
49
|
-
raise TypeError(f"{cls.__name__} must directly inherit from
|
|
48
|
+
if BrainlessBucket not in cls.__bases__:
|
|
49
|
+
raise TypeError(f"{cls.__name__} must directly inherit from BrainlessBucket")
|
|
50
50
|
|
|
51
51
|
self._client = client
|
|
52
52
|
self._cls = cls
|
|
@@ -38,11 +38,11 @@ def _unregister(uuid: UUID) -> None:
|
|
|
38
38
|
_registry.pop(uuid, None)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
class
|
|
41
|
+
class BrainlessStruct(Struct, tag=True, tag_field="TYPE"):
|
|
42
42
|
"""Base struct for nested/embedded types."""
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
class
|
|
45
|
+
class BrainlessBucket(Struct, kw_only=True, tag=True, tag_field="TYPE"):
|
|
46
46
|
"""Base struct for brainlessdb entities."""
|
|
47
47
|
|
|
48
48
|
_uuid: UUID = field(default_factory=uuid1)
|
|
@@ -57,7 +57,7 @@ class BrainlessStruct(Struct, kw_only=True, tag=True, tag_field="TYPE"):
|
|
|
57
57
|
if collection:
|
|
58
58
|
collection.add(self)
|
|
59
59
|
|
|
60
|
-
def update(self, data: dict) -> "
|
|
60
|
+
def update(self, data: dict) -> "BrainlessBucket":
|
|
61
61
|
"""Update fields from dict, supports nested structs."""
|
|
62
62
|
for key, value in data.items():
|
|
63
63
|
if not hasattr(self, key):
|
|
@@ -6,7 +6,7 @@ create-changelog:
|
|
|
6
6
|
release: create-changelog
|
|
7
7
|
awk -i inplace '{ sub(/^version = "[0-9]+\.[0-9]+\.[0-9]+"/, "version = \"{{next_version}}\"") }; { print }' pyproject.toml
|
|
8
8
|
awk -i inplace '{ sub(/^__version__ = "[0-9]+\.[0-9]+\.[0-9]+"/, "__version__ = \"{{next_version}}\"") }; { print }' brainlessdb/__init__.py
|
|
9
|
-
git add --ignore-errors CHANGELOG.md
|
|
9
|
+
git add --ignore-errors CHANGELOG.md pyproject.toml uv.lock brainlessdb/__init__.py
|
|
10
10
|
git commit -m {{next_version}}
|
|
11
11
|
git tag -a {{next_version}} -m {{next_version}}
|
|
12
12
|
|
|
@@ -425,14 +425,14 @@ self = <tests.test_deserialization.TestRealWorldUserPattern object at 0x720273f1
|
|
|
425
425
|
"""Test Optional[Annotated[int, Meta(...)]] pattern like queue_reason."""
|
|
426
426
|
from enum import IntEnum
|
|
427
427
|
import msgspec
|
|
428
|
-
from brainlessdb import BrainlessDB, BrainlessDBFeat,
|
|
428
|
+
from brainlessdb import BrainlessDB, BrainlessDBFeat, BrainlessBucket
|
|
429
429
|
from brainlessdb.struct import ConfigWrapper
|
|
430
|
-
|
|
430
|
+
|
|
431
431
|
class Status(IntEnum):
|
|
432
432
|
OFFLINE = 0
|
|
433
433
|
ONLINE = 1
|
|
434
|
-
|
|
435
|
-
class UserLike(
|
|
434
|
+
|
|
435
|
+
class UserLike(BrainlessBucket):
|
|
436
436
|
id: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX})] = 0
|
|
437
437
|
group_id: int = 0
|
|
438
438
|
# Pattern from queue_reason - Optional[Annotated[int, ...]]
|
|
@@ -440,7 +440,7 @@ self = <tests.test_deserialization.TestRealWorldUserPattern object at 0x720273f1
|
|
|
440
440
|
queue_status_changed: Optional[Annotated[int, Meta(description="")]] = None
|
|
441
441
|
# State field with enum
|
|
442
442
|
status: Annotated[Status, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.STATE})] = Status.OFFLINE
|
|
443
|
-
|
|
443
|
+
|
|
444
444
|
db = BrainlessDB(namespace="test")
|
|
445
445
|
> coll = await db.collection(UserLike)
|
|
446
446
|
E TypeError: object Collection can't be used in 'await' expression
|
|
@@ -454,17 +454,17 @@ self = <tests.test_deserialization.TestRealWorldUserPattern object at 0x720273f1
|
|
|
454
454
|
async def test_nested_struct_pattern(self):
|
|
455
455
|
"""Test nested struct like AgentChannelsV1."""
|
|
456
456
|
import msgspec
|
|
457
|
-
from brainlessdb import BrainlessDB,
|
|
457
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
458
458
|
from brainlessdb.struct import ConfigWrapper
|
|
459
|
-
|
|
459
|
+
|
|
460
460
|
class Channels(Struct):
|
|
461
461
|
voice: int = 0
|
|
462
462
|
chat: int = 0
|
|
463
|
-
|
|
464
|
-
class UserWithChannels(
|
|
463
|
+
|
|
464
|
+
class UserWithChannels(BrainlessBucket):
|
|
465
465
|
id: int = 0
|
|
466
466
|
channels: Channels = field(default_factory=Channels)
|
|
467
|
-
|
|
467
|
+
|
|
468
468
|
db = BrainlessDB(namespace="test")
|
|
469
469
|
> coll = await db.collection(UserWithChannels)
|
|
470
470
|
E TypeError: object Collection can't be used in 'await' expression
|
|
@@ -478,17 +478,17 @@ self = <tests.test_deserialization.TestRealWorldUserPattern object at 0x720273f1
|
|
|
478
478
|
async def test_vcard_list_pattern(self):
|
|
479
479
|
"""Test list of nested structs like vcard: list[VCardV1]."""
|
|
480
480
|
import msgspec
|
|
481
|
-
from brainlessdb import BrainlessDB,
|
|
481
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
482
482
|
from brainlessdb.struct import ConfigWrapper
|
|
483
|
-
|
|
483
|
+
|
|
484
484
|
class VCard(Struct):
|
|
485
485
|
name: str = ""
|
|
486
486
|
value: str = ""
|
|
487
|
-
|
|
488
|
-
class UserWithVCard(
|
|
487
|
+
|
|
488
|
+
class UserWithVCard(BrainlessBucket):
|
|
489
489
|
id: int = 0
|
|
490
490
|
vcard: Optional[list[VCard]] = None
|
|
491
|
-
|
|
491
|
+
|
|
492
492
|
db = BrainlessDB(namespace="test")
|
|
493
493
|
> coll = await db.collection(UserWithVCard)
|
|
494
494
|
E TypeError: object Collection can't be used in 'await' expression
|
|
@@ -501,18 +501,18 @@ self = <tests.test_deserialization.TestLocalClassDetection object at 0x720273f19
|
|
|
501
501
|
@pytest.mark.asyncio
|
|
502
502
|
async def test_optional_annotated_union_pattern(self):
|
|
503
503
|
"""Test Optional[Annotated[Union[A, B], Meta()]] pattern from UserV2."""
|
|
504
|
-
from brainlessdb import BrainlessDB,
|
|
505
|
-
|
|
504
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
505
|
+
|
|
506
506
|
class AriLocal(Struct):
|
|
507
507
|
counter: int = 0
|
|
508
|
-
|
|
508
|
+
|
|
509
509
|
class UcsLocal(Struct):
|
|
510
510
|
counter: int = 0
|
|
511
|
-
|
|
512
|
-
class UserWithUnionLocal(
|
|
511
|
+
|
|
512
|
+
class UserWithUnionLocal(BrainlessBucket):
|
|
513
513
|
id: int = 0
|
|
514
514
|
_: Optional[Annotated[Union[UcsLocal, AriLocal], Meta()]] = None
|
|
515
|
-
|
|
515
|
+
|
|
516
516
|
# Test with "ari" namespace - should find AriLocal
|
|
517
517
|
db_ari = BrainlessDB(namespace="ari")
|
|
518
518
|
> coll_ari = await db_ari.collection(UserWithUnionLocal)
|
|
@@ -526,15 +526,15 @@ self = <tests.test_deserialization.TestLocalClassDetection object at 0x720273f19
|
|
|
526
526
|
@pytest.mark.asyncio
|
|
527
527
|
async def test_optional_single_class_pattern(self):
|
|
528
528
|
"""Test Optional[LocalClass] pattern."""
|
|
529
|
-
from brainlessdb import BrainlessDB,
|
|
530
|
-
|
|
529
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
530
|
+
|
|
531
531
|
class TestLocal(Struct):
|
|
532
532
|
value: int = 0
|
|
533
|
-
|
|
534
|
-
class UserWithLocal(
|
|
533
|
+
|
|
534
|
+
class UserWithLocal(BrainlessBucket):
|
|
535
535
|
id: int = 0
|
|
536
536
|
_: Optional[TestLocal] = None
|
|
537
|
-
|
|
537
|
+
|
|
538
538
|
db = BrainlessDB(namespace="test")
|
|
539
539
|
> coll = await db.collection(UserWithLocal)
|
|
540
540
|
E TypeError: object Collection can't be used in 'await' expression
|
|
@@ -547,18 +547,18 @@ self = <tests.test_deserialization.TestLocalClassDetection object at 0x720273f12
|
|
|
547
547
|
@pytest.mark.asyncio
|
|
548
548
|
async def test_union_without_optional_pattern(self):
|
|
549
549
|
"""Test Union[A, B] (without Optional) pattern."""
|
|
550
|
-
from brainlessdb import BrainlessDB,
|
|
551
|
-
|
|
550
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
551
|
+
|
|
552
552
|
class AriData(Struct):
|
|
553
553
|
x: int = 0
|
|
554
|
-
|
|
554
|
+
|
|
555
555
|
class UcsData(Struct):
|
|
556
556
|
x: int = 0
|
|
557
|
-
|
|
558
|
-
class UserUnion(
|
|
557
|
+
|
|
558
|
+
class UserUnion(BrainlessBucket):
|
|
559
559
|
id: int = 0
|
|
560
560
|
_: Union[UcsData, AriData] = field(default_factory=UcsData)
|
|
561
|
-
|
|
561
|
+
|
|
562
562
|
db = BrainlessDB(namespace="ari")
|
|
563
563
|
> coll = await db.collection(UserUnion)
|
|
564
564
|
E TypeError: object Collection can't be used in 'await' expression
|
|
@@ -572,12 +572,12 @@ self = <tests.test_sync.TestMultiLocationSync object at 0x720273f197c0>
|
|
|
572
572
|
async def test_two_locations_see_same_data(self):
|
|
573
573
|
"""Both locations can read data written by either."""
|
|
574
574
|
nats = MockNats()
|
|
575
|
-
|
|
575
|
+
|
|
576
576
|
db1 = BrainlessDB(nats, namespace="loc1", flush_interval=0)
|
|
577
577
|
db2 = BrainlessDB(nats, namespace="loc2", flush_interval=0)
|
|
578
578
|
await db1.start()
|
|
579
579
|
await db2.start()
|
|
580
|
-
|
|
580
|
+
|
|
581
581
|
> users1 = await db1.collection(UserV1)
|
|
582
582
|
E TypeError: object Collection can't be used in 'await' expression
|
|
583
583
|
|
|
@@ -590,12 +590,12 @@ self = <tests.test_sync.TestMultiLocationSync object at 0x720273f125b0>
|
|
|
590
590
|
async def test_watch_receives_remote_changes(self):
|
|
591
591
|
"""Watch fires when other location makes changes."""
|
|
592
592
|
nats = MockNats()
|
|
593
|
-
|
|
593
|
+
|
|
594
594
|
db1 = BrainlessDB(nats, namespace="loc1", flush_interval=0)
|
|
595
595
|
db2 = BrainlessDB(nats, namespace="loc2", flush_interval=0)
|
|
596
596
|
await db1.start()
|
|
597
597
|
await db2.start()
|
|
598
|
-
|
|
598
|
+
|
|
599
599
|
> users1 = await db1.collection(UserV1)
|
|
600
600
|
E TypeError: object Collection can't be used in 'await' expression
|
|
601
601
|
|
|
@@ -608,10 +608,10 @@ self = <tests.test_sync.TestMultiLocationSync object at 0x720273eadd00>
|
|
|
608
608
|
async def test_location_tracked_in_metadata(self):
|
|
609
609
|
"""Config bucket tracks which location made the change."""
|
|
610
610
|
nats = MockNats()
|
|
611
|
-
|
|
611
|
+
|
|
612
612
|
db1 = BrainlessDB(nats, namespace="loc1", flush_interval=0)
|
|
613
613
|
await db1.start()
|
|
614
|
-
|
|
614
|
+
|
|
615
615
|
> users1 = await db1.collection(UserV1)
|
|
616
616
|
E TypeError: object Collection can't be used in 'await' expression
|
|
617
617
|
|
|
@@ -624,12 +624,12 @@ self = <tests.test_sync.TestMultiLocationSync object at 0x720273ead040>
|
|
|
624
624
|
async def test_remote_delete_fires_event(self):
|
|
625
625
|
"""Delete from one location fires event on other."""
|
|
626
626
|
nats = MockNats()
|
|
627
|
-
|
|
627
|
+
|
|
628
628
|
db1 = BrainlessDB(nats, namespace="loc1", flush_interval=0)
|
|
629
629
|
db2 = BrainlessDB(nats, namespace="loc2", flush_interval=0)
|
|
630
630
|
await db1.start()
|
|
631
631
|
await db2.start()
|
|
632
|
-
|
|
632
|
+
|
|
633
633
|
> users1 = await db1.collection(UserV1)
|
|
634
634
|
E TypeError: object Collection can't be used in 'await' expression
|
|
635
635
|
|
|
@@ -5,10 +5,10 @@ from typing import Annotated, Optional
|
|
|
5
5
|
import pytest
|
|
6
6
|
from msgspec import Meta
|
|
7
7
|
|
|
8
|
-
from brainlessdb import BrainlessDB, BrainlessDBFeat,
|
|
8
|
+
from brainlessdb import BrainlessDB, BrainlessDBFeat, BrainlessBucket
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class UserV1(
|
|
11
|
+
class UserV1(BrainlessBucket):
|
|
12
12
|
"""Test user struct with indexed username."""
|
|
13
13
|
|
|
14
14
|
id: Annotated[int, Meta()] = 0
|
|
@@ -20,21 +20,21 @@ class UserV1(BrainlessStruct):
|
|
|
20
20
|
active: Annotated[bool, Meta()] = True
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class ChannelV1(
|
|
23
|
+
class ChannelV1(BrainlessBucket):
|
|
24
24
|
"""Test channel struct."""
|
|
25
25
|
|
|
26
26
|
id: Annotated[str, Meta()] = ""
|
|
27
27
|
name: Annotated[str, Meta()] = ""
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class AgentV1(
|
|
30
|
+
class AgentV1(BrainlessBucket):
|
|
31
31
|
"""Test agent struct."""
|
|
32
32
|
|
|
33
33
|
id: Annotated[int, Meta()] = 0
|
|
34
34
|
channel: Optional[ChannelV1] = None
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
class QueueItemV1(
|
|
37
|
+
class QueueItemV1(BrainlessBucket):
|
|
38
38
|
"""Test queue item with nested structs."""
|
|
39
39
|
|
|
40
40
|
inbound_id: Annotated[
|
|
@@ -45,7 +45,7 @@ class QueueItemV1(BrainlessStruct):
|
|
|
45
45
|
agents: Optional[AgentV1] = None
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class UniqueUserV1(
|
|
48
|
+
class UniqueUserV1(BrainlessBucket):
|
|
49
49
|
"""Test struct with UNIQUE field."""
|
|
50
50
|
|
|
51
51
|
id: int = 0
|
|
@@ -525,9 +525,9 @@ class TestOptionalFieldRoundTrip:
|
|
|
525
525
|
"""Optional field set to None must be stored and restored."""
|
|
526
526
|
from typing import Optional
|
|
527
527
|
|
|
528
|
-
from brainlessdb import BrainlessDB,
|
|
528
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
529
529
|
|
|
530
|
-
class DeviceV1(
|
|
530
|
+
class DeviceV1(BrainlessBucket):
|
|
531
531
|
name: str = ""
|
|
532
532
|
host_override: Optional[str] = None
|
|
533
533
|
|
|
@@ -553,10 +553,10 @@ class TestOptionalFieldRoundTrip:
|
|
|
553
553
|
|
|
554
554
|
import msgspec
|
|
555
555
|
|
|
556
|
-
from brainlessdb import BrainlessDB,
|
|
556
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
557
557
|
from brainlessdb.struct import ConfigWrapper
|
|
558
558
|
|
|
559
|
-
class DeviceV1(
|
|
559
|
+
class DeviceV1(BrainlessBucket):
|
|
560
560
|
name: str = ""
|
|
561
561
|
host_override: Optional[str] = None
|
|
562
562
|
|
|
@@ -582,10 +582,10 @@ class TestOptionalFieldRoundTrip:
|
|
|
582
582
|
"""Full flush/reload through mock NATS preserves None fields."""
|
|
583
583
|
from typing import Optional
|
|
584
584
|
|
|
585
|
-
from brainlessdb import BrainlessDB,
|
|
585
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
586
586
|
from tests.mock_nats import MockNats
|
|
587
587
|
|
|
588
|
-
class DeviceV1(
|
|
588
|
+
class DeviceV1(BrainlessBucket):
|
|
589
589
|
name: str = ""
|
|
590
590
|
host_override: Optional[str] = None
|
|
591
591
|
|
|
@@ -9,7 +9,7 @@ from typing import Annotated, Optional, Union
|
|
|
9
9
|
import pytest
|
|
10
10
|
from msgspec import Meta, Struct, field
|
|
11
11
|
|
|
12
|
-
from brainlessdb import BrainlessDB, BrainlessDBFeat,
|
|
12
|
+
from brainlessdb import BrainlessDB, BrainlessDBFeat, BrainlessBucket
|
|
13
13
|
from brainlessdb.collection import Collection
|
|
14
14
|
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ class LocalData(Struct):
|
|
|
20
20
|
count: int = 0
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class EntityWithIntFields(
|
|
23
|
+
class EntityWithIntFields(BrainlessBucket):
|
|
24
24
|
"""Entity with various int fields in different buckets."""
|
|
25
25
|
|
|
26
26
|
# Config field (no flags)
|
|
@@ -48,7 +48,7 @@ class EntityWithIntFields(BrainlessStruct):
|
|
|
48
48
|
_: Optional[LocalData] = None
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
class EntityMixedTypes(
|
|
51
|
+
class EntityMixedTypes(BrainlessBucket):
|
|
52
52
|
"""Entity with mixed types to test type preservation."""
|
|
53
53
|
|
|
54
54
|
int_field: int = 0
|
|
@@ -427,14 +427,14 @@ class TestRealWorldUserPattern:
|
|
|
427
427
|
"""Test Optional[Annotated[int, Meta(...)]] pattern like queue_reason."""
|
|
428
428
|
from enum import IntEnum
|
|
429
429
|
import msgspec
|
|
430
|
-
from brainlessdb import BrainlessDB, BrainlessDBFeat,
|
|
430
|
+
from brainlessdb import BrainlessDB, BrainlessDBFeat, BrainlessBucket
|
|
431
431
|
from brainlessdb.struct import ConfigWrapper
|
|
432
432
|
|
|
433
433
|
class Status(IntEnum):
|
|
434
434
|
OFFLINE = 0
|
|
435
435
|
ONLINE = 1
|
|
436
436
|
|
|
437
|
-
class UserLike(
|
|
437
|
+
class UserLike(BrainlessBucket):
|
|
438
438
|
id: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.INDEX})] = 0
|
|
439
439
|
group_id: int = 0
|
|
440
440
|
# Pattern from queue_reason - Optional[Annotated[int, ...]]
|
|
@@ -490,14 +490,14 @@ class TestRealWorldUserPattern:
|
|
|
490
490
|
async def test_nested_struct_pattern(self):
|
|
491
491
|
"""Test nested struct like AgentChannelsV1."""
|
|
492
492
|
import msgspec
|
|
493
|
-
from brainlessdb import BrainlessDB,
|
|
493
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
494
494
|
from brainlessdb.struct import ConfigWrapper
|
|
495
495
|
|
|
496
496
|
class Channels(Struct):
|
|
497
497
|
voice: int = 0
|
|
498
498
|
chat: int = 0
|
|
499
499
|
|
|
500
|
-
class UserWithChannels(
|
|
500
|
+
class UserWithChannels(BrainlessBucket):
|
|
501
501
|
id: int = 0
|
|
502
502
|
channels: Channels = field(default_factory=Channels)
|
|
503
503
|
|
|
@@ -526,14 +526,14 @@ class TestRealWorldUserPattern:
|
|
|
526
526
|
async def test_vcard_list_pattern(self):
|
|
527
527
|
"""Test list of nested structs like vcard: list[VCardV1]."""
|
|
528
528
|
import msgspec
|
|
529
|
-
from brainlessdb import BrainlessDB,
|
|
529
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
530
530
|
from brainlessdb.struct import ConfigWrapper
|
|
531
531
|
|
|
532
532
|
class VCard(Struct):
|
|
533
533
|
name: str = ""
|
|
534
534
|
value: str = ""
|
|
535
535
|
|
|
536
|
-
class UserWithVCard(
|
|
536
|
+
class UserWithVCard(BrainlessBucket):
|
|
537
537
|
id: int = 0
|
|
538
538
|
vcard: Optional[list[VCard]] = None
|
|
539
539
|
|
|
@@ -568,7 +568,7 @@ class TestLocalClassDetection:
|
|
|
568
568
|
@pytest.mark.asyncio
|
|
569
569
|
async def test_optional_annotated_union_pattern(self):
|
|
570
570
|
"""Test Optional[Annotated[Union[A, B], Meta()]] pattern from UserV2."""
|
|
571
|
-
from brainlessdb import BrainlessDB,
|
|
571
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
572
572
|
|
|
573
573
|
class AriLocal(Struct):
|
|
574
574
|
counter: int = 0
|
|
@@ -576,7 +576,7 @@ class TestLocalClassDetection:
|
|
|
576
576
|
class UcsLocal(Struct):
|
|
577
577
|
counter: int = 0
|
|
578
578
|
|
|
579
|
-
class UserWithUnionLocal(
|
|
579
|
+
class UserWithUnionLocal(BrainlessBucket):
|
|
580
580
|
id: int = 0
|
|
581
581
|
_: Optional[Annotated[Union[UcsLocal, AriLocal], Meta()]] = None
|
|
582
582
|
|
|
@@ -593,12 +593,12 @@ class TestLocalClassDetection:
|
|
|
593
593
|
@pytest.mark.asyncio
|
|
594
594
|
async def test_optional_single_class_pattern(self):
|
|
595
595
|
"""Test Optional[LocalClass] pattern."""
|
|
596
|
-
from brainlessdb import BrainlessDB,
|
|
596
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
597
597
|
|
|
598
598
|
class TestLocal(Struct):
|
|
599
599
|
value: int = 0
|
|
600
600
|
|
|
601
|
-
class UserWithLocal(
|
|
601
|
+
class UserWithLocal(BrainlessBucket):
|
|
602
602
|
id: int = 0
|
|
603
603
|
_: Optional[TestLocal] = None
|
|
604
604
|
|
|
@@ -609,7 +609,7 @@ class TestLocalClassDetection:
|
|
|
609
609
|
@pytest.mark.asyncio
|
|
610
610
|
async def test_union_without_optional_pattern(self):
|
|
611
611
|
"""Test Union[A, B] (without Optional) pattern."""
|
|
612
|
-
from brainlessdb import BrainlessDB,
|
|
612
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
613
613
|
|
|
614
614
|
class AriData(Struct):
|
|
615
615
|
x: int = 0
|
|
@@ -617,7 +617,7 @@ class TestLocalClassDetection:
|
|
|
617
617
|
class UcsData(Struct):
|
|
618
618
|
x: int = 0
|
|
619
619
|
|
|
620
|
-
class UserUnion(
|
|
620
|
+
class UserUnion(BrainlessBucket):
|
|
621
621
|
id: int = 0
|
|
622
622
|
_: Union[UcsData, AriData] = field(default_factory=UcsData)
|
|
623
623
|
|
|
@@ -4,7 +4,7 @@ from typing import Annotated, Optional, Union
|
|
|
4
4
|
|
|
5
5
|
from msgspec import Meta, Struct
|
|
6
6
|
|
|
7
|
-
from brainlessdb import BrainlessDBFeat,
|
|
7
|
+
from brainlessdb import BrainlessDBFeat, BrainlessBucket
|
|
8
8
|
from brainlessdb.fields import analyze_fields
|
|
9
9
|
|
|
10
10
|
|
|
@@ -17,14 +17,14 @@ class AriV1(Struct):
|
|
|
17
17
|
channel_id: str = ""
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class SimpleV1(
|
|
20
|
+
class SimpleV1(BrainlessBucket):
|
|
21
21
|
"""Simple struct with no state or app-local."""
|
|
22
22
|
|
|
23
23
|
id: Annotated[int, Meta()] = 0
|
|
24
24
|
name: Annotated[str, Meta()] = ""
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class WithStateV1(
|
|
27
|
+
class WithStateV1(BrainlessBucket):
|
|
28
28
|
"""Struct with state fields."""
|
|
29
29
|
|
|
30
30
|
id: Annotated[int, Meta()] = 0
|
|
@@ -39,21 +39,21 @@ class WithStateV1(BrainlessStruct):
|
|
|
39
39
|
] = 0
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
class WithLocalV1(
|
|
42
|
+
class WithLocalV1(BrainlessBucket):
|
|
43
43
|
"""Struct with app-local field."""
|
|
44
44
|
|
|
45
45
|
id: Annotated[int, Meta()] = 0
|
|
46
46
|
_: Optional[UcsV1] = None
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
class WithUnionLocalV1(
|
|
49
|
+
class WithUnionLocalV1(BrainlessBucket):
|
|
50
50
|
"""Struct with Union app-local field."""
|
|
51
51
|
|
|
52
52
|
id: Annotated[int, Meta()] = 0
|
|
53
53
|
_: Union[UcsV1, AriV1, None] = None
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
class FullV1(
|
|
56
|
+
class FullV1(BrainlessBucket):
|
|
57
57
|
"""Struct with config, state, and app-local."""
|
|
58
58
|
|
|
59
59
|
id: Annotated[int, Meta()] = 0
|
|
@@ -125,9 +125,9 @@ class TestCorruptStateBucket:
|
|
|
125
125
|
|
|
126
126
|
from msgspec import Meta
|
|
127
127
|
|
|
128
|
-
from brainlessdb import BrainlessDBFeat,
|
|
128
|
+
from brainlessdb import BrainlessDBFeat, BrainlessBucket
|
|
129
129
|
|
|
130
|
-
class AgentV1(
|
|
130
|
+
class AgentV1(BrainlessBucket):
|
|
131
131
|
id: int = 0
|
|
132
132
|
status: Annotated[int, Meta(extra={"brainlessdb_flags": BrainlessDBFeat.STATE})] = 0
|
|
133
133
|
|
|
@@ -171,12 +171,12 @@ class TestCorruptLocalBucket:
|
|
|
171
171
|
|
|
172
172
|
from msgspec import Struct
|
|
173
173
|
|
|
174
|
-
from brainlessdb import BrainlessDB,
|
|
174
|
+
from brainlessdb import BrainlessDB, BrainlessBucket
|
|
175
175
|
|
|
176
176
|
class TestLocal(Struct):
|
|
177
177
|
sid: int = 0
|
|
178
178
|
|
|
179
|
-
class ItemV1(
|
|
179
|
+
class ItemV1(BrainlessBucket):
|
|
180
180
|
id: int = 0
|
|
181
181
|
_: Optional[TestLocal] = None
|
|
182
182
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|