ic-python-db 0.12.0__tar.gz → 0.13.0__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.
- {ic_python_db-0.12.0/ic_python_db.egg-info → ic_python_db-0.13.0}/PKG-INFO +30 -7
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/README.md +29 -6
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/__init__.py +5 -1
- ic_python_db-0.13.0/ic_python_db/constants.py +11 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/db_engine.py +338 -95
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/entity.py +133 -36
- ic_python_db-0.13.0/ic_python_db/errors.py +10 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/properties.py +158 -59
- {ic_python_db-0.12.0 → ic_python_db-0.13.0/ic_python_db.egg-info}/PKG-INFO +30 -7
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db.egg-info/SOURCES.txt +1 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/pyproject.toml +1 -1
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/setup.py +1 -1
- ic_python_db-0.12.0/ic_python_db/constants.py +0 -6
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/LICENSE +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/MANIFEST.in +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/_cdk.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/context.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/hooks.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/mixins.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/py.typed +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/schema.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/storage.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db/system_time.py +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db.egg-info/dependency_links.txt +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/ic_python_db.egg-info/top_level.txt +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/requirements-dev.txt +0 -0
- {ic_python_db-0.12.0 → ic_python_db-0.13.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ic-python-db
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: A lightweight key-value database written in Python, intended for use on the Internet Computer (IC)
|
|
5
5
|
Home-page: https://github.com/smart-social-contracts/ic-python-db
|
|
6
6
|
Author: Smart Social Contracts
|
|
@@ -60,7 +60,7 @@ A lightweight key-value database with entity relationships and audit logging cap
|
|
|
60
60
|
## Features
|
|
61
61
|
|
|
62
62
|
- **Persistent Storage**: Works with StableBTreeMap stable structure for persistent storage on your canister's stable memory so your data persists automatically across canister upgrades.
|
|
63
|
-
- **Range-based paging**: `
|
|
63
|
+
- **Range-based paging**: `page`, `load_some` and `rebuild_field_index` read a page of entities with a single ordered `range` call on the stable map (Basilisk `StableBTreeMap.range`), so cost tracks the rows returned rather than the highest id ever issued, and deleted ids are free. Backends without `range` (older CDK builds, plain dict test doubles) transparently fall back to one read per id. `instances()` and `find()` load a whole type: they are deprecated and raise `TooLargeError` past 1,000 entities (use `count()`, `find_by()` or `page()`).
|
|
64
64
|
- **Entity-Relational Database**: Create, read and write entities with OneToOne, OneToMany, ManyToOne, and ManyToMany relationships.
|
|
65
65
|
- **Schema Versioning & Upgrade Safety**: Automatic schema introspection, compatibility checking, and auto-migration for safe changes. Breaking changes without a `migrate()` method are rejected, preventing data corruption on canister upgrades.
|
|
66
66
|
- **Entity Hooks**: Intercept and control entity lifecycle events (create, modify, delete) with `on_event` hooks.
|
|
@@ -163,11 +163,12 @@ Then use the defined entities to store objects:
|
|
|
163
163
|
|
|
164
164
|
assert Person.count() == 3
|
|
165
165
|
assert Person.max_id() == 4
|
|
166
|
-
assert Person.instances() == [john, alice, eva]
|
|
167
166
|
|
|
168
|
-
# Cursor-based pagination
|
|
169
|
-
|
|
170
|
-
assert
|
|
167
|
+
# Cursor-based pagination: (page, next_from_id), None on the last page
|
|
168
|
+
page, cursor = Person.page(1, 2)
|
|
169
|
+
assert page == [john, alice]
|
|
170
|
+
page, cursor = Person.page(cursor, 2)
|
|
171
|
+
assert page == [eva] and cursor is None
|
|
171
172
|
|
|
172
173
|
# Retrieve database contents in JSON format
|
|
173
174
|
print(Database.get_instance().dump_json(pretty=True))
|
|
@@ -229,7 +230,29 @@ while cursor is not None:
|
|
|
229
230
|
|
|
230
231
|
The rebuild is idempotent. The `indexed` flag is not part of the schema descriptor, so toggling it never triggers upgrade-compatibility errors.
|
|
231
232
|
|
|
232
|
-
|
|
233
|
+
Each matching id is its own storage entry, so a value can match any number of entities without reaching the backing store's `max_value_size`.
|
|
234
|
+
|
|
235
|
+
## Large Relations
|
|
236
|
+
|
|
237
|
+
`OneToMany` and `ManyToMany` getters return a `RelationView`, not a list. Questions about the relation and single pages never load every member, so they stay cheap however large it grows:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
if section.stands: ... # one read
|
|
241
|
+
len(section.stands) # one read
|
|
242
|
+
stand in section.stands # two reads
|
|
243
|
+
stands, cursor = section.stands.page(1, 50) # one range read + 50 loads
|
|
244
|
+
group.members.add(user) # index writes only
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Iterating, indexing and comparing still work, but they load every member, so past `constants.MAX_UNPAGED` (1,000) they raise `TooLargeError` instead of running into the IC instruction limit. Walk a large relation with `page()`, across calls if needed:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
cursor = 1
|
|
251
|
+
while cursor is not None:
|
|
252
|
+
stands, cursor = section.stands.page(cursor, 50)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`list(view)` gives a plain list. Deleting a parent is O(1): its index entries are purged in bounded steps by later writes, or by `Database.get_instance().purge_step()` from a timer.
|
|
233
256
|
|
|
234
257
|
## Namespaces
|
|
235
258
|
|
|
@@ -11,7 +11,7 @@ A lightweight key-value database with entity relationships and audit logging cap
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Persistent Storage**: Works with StableBTreeMap stable structure for persistent storage on your canister's stable memory so your data persists automatically across canister upgrades.
|
|
14
|
-
- **Range-based paging**: `
|
|
14
|
+
- **Range-based paging**: `page`, `load_some` and `rebuild_field_index` read a page of entities with a single ordered `range` call on the stable map (Basilisk `StableBTreeMap.range`), so cost tracks the rows returned rather than the highest id ever issued, and deleted ids are free. Backends without `range` (older CDK builds, plain dict test doubles) transparently fall back to one read per id. `instances()` and `find()` load a whole type: they are deprecated and raise `TooLargeError` past 1,000 entities (use `count()`, `find_by()` or `page()`).
|
|
15
15
|
- **Entity-Relational Database**: Create, read and write entities with OneToOne, OneToMany, ManyToOne, and ManyToMany relationships.
|
|
16
16
|
- **Schema Versioning & Upgrade Safety**: Automatic schema introspection, compatibility checking, and auto-migration for safe changes. Breaking changes without a `migrate()` method are rejected, preventing data corruption on canister upgrades.
|
|
17
17
|
- **Entity Hooks**: Intercept and control entity lifecycle events (create, modify, delete) with `on_event` hooks.
|
|
@@ -114,11 +114,12 @@ Then use the defined entities to store objects:
|
|
|
114
114
|
|
|
115
115
|
assert Person.count() == 3
|
|
116
116
|
assert Person.max_id() == 4
|
|
117
|
-
assert Person.instances() == [john, alice, eva]
|
|
118
117
|
|
|
119
|
-
# Cursor-based pagination
|
|
120
|
-
|
|
121
|
-
assert
|
|
118
|
+
# Cursor-based pagination: (page, next_from_id), None on the last page
|
|
119
|
+
page, cursor = Person.page(1, 2)
|
|
120
|
+
assert page == [john, alice]
|
|
121
|
+
page, cursor = Person.page(cursor, 2)
|
|
122
|
+
assert page == [eva] and cursor is None
|
|
122
123
|
|
|
123
124
|
# Retrieve database contents in JSON format
|
|
124
125
|
print(Database.get_instance().dump_json(pretty=True))
|
|
@@ -180,7 +181,29 @@ while cursor is not None:
|
|
|
180
181
|
|
|
181
182
|
The rebuild is idempotent. The `indexed` flag is not part of the schema descriptor, so toggling it never triggers upgrade-compatibility errors.
|
|
182
183
|
|
|
183
|
-
|
|
184
|
+
Each matching id is its own storage entry, so a value can match any number of entities without reaching the backing store's `max_value_size`.
|
|
185
|
+
|
|
186
|
+
## Large Relations
|
|
187
|
+
|
|
188
|
+
`OneToMany` and `ManyToMany` getters return a `RelationView`, not a list. Questions about the relation and single pages never load every member, so they stay cheap however large it grows:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
if section.stands: ... # one read
|
|
192
|
+
len(section.stands) # one read
|
|
193
|
+
stand in section.stands # two reads
|
|
194
|
+
stands, cursor = section.stands.page(1, 50) # one range read + 50 loads
|
|
195
|
+
group.members.add(user) # index writes only
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Iterating, indexing and comparing still work, but they load every member, so past `constants.MAX_UNPAGED` (1,000) they raise `TooLargeError` instead of running into the IC instruction limit. Walk a large relation with `page()`, across calls if needed:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
cursor = 1
|
|
202
|
+
while cursor is not None:
|
|
203
|
+
stands, cursor = section.stands.page(cursor, 50)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`list(view)` gives a plain list. Deleting a parent is O(1): its index entries are purged in bounded steps by later writes, or by `Database.get_instance().purge_step()` from a timer.
|
|
184
207
|
|
|
185
208
|
## Namespaces
|
|
186
209
|
|
|
@@ -5,6 +5,7 @@ IC Python DB - A lightweight key-value database with entity relationships and au
|
|
|
5
5
|
from .constants import ACTION_CREATE, ACTION_DELETE, ACTION_MODIFY
|
|
6
6
|
from .db_engine import Database
|
|
7
7
|
from .entity import Entity
|
|
8
|
+
from .errors import TooLargeError
|
|
8
9
|
from .mixins import TimestampedMixin
|
|
9
10
|
from .properties import (
|
|
10
11
|
Boolean,
|
|
@@ -14,13 +15,14 @@ from .properties import (
|
|
|
14
15
|
ManyToOne,
|
|
15
16
|
OneToMany,
|
|
16
17
|
OneToOne,
|
|
18
|
+
RelationView,
|
|
17
19
|
String,
|
|
18
20
|
)
|
|
19
21
|
from .schema import SchemaIncompatibleError, build_schema, diff_schemas, schema_hash
|
|
20
22
|
from .storage import MemoryStorage, Storage
|
|
21
23
|
from .system_time import SystemTime
|
|
22
24
|
|
|
23
|
-
__version__ = "0.
|
|
25
|
+
__version__ = "0.13.0"
|
|
24
26
|
__all__ = [
|
|
25
27
|
"Database",
|
|
26
28
|
"Entity",
|
|
@@ -40,6 +42,8 @@ __all__ = [
|
|
|
40
42
|
"ACTION_MODIFY",
|
|
41
43
|
"ACTION_DELETE",
|
|
42
44
|
"SchemaIncompatibleError",
|
|
45
|
+
"TooLargeError",
|
|
46
|
+
"RelationView",
|
|
43
47
|
"build_schema",
|
|
44
48
|
"diff_schemas",
|
|
45
49
|
"schema_hash",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LEVEL_MAX_DEFAULT = 3
|
|
2
|
+
|
|
3
|
+
# Hook action types
|
|
4
|
+
ACTION_CREATE = "create"
|
|
5
|
+
ACTION_MODIFY = "modify"
|
|
6
|
+
ACTION_DELETE = "delete"
|
|
7
|
+
|
|
8
|
+
# Largest collection an unpaged operation (iterating a relation, instances())
|
|
9
|
+
# loads in one call; past it they raise TooLargeError. At about 1M
|
|
10
|
+
# instructions per load this stays well inside a query's 5B budget.
|
|
11
|
+
MAX_UNPAGED = 1000
|