ic-python-db 0.11.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.11.0/ic_python_db.egg-info → ic_python_db-0.13.0}/PKG-INFO +30 -6
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/README.md +29 -5
- {ic_python_db-0.11.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.13.0/ic_python_db/db_engine.py +927 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/entity.py +183 -66
- ic_python_db-0.13.0/ic_python_db/errors.py +10 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/properties.py +158 -59
- ic_python_db-0.13.0/ic_python_db/storage.py +118 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0/ic_python_db.egg-info}/PKG-INFO +30 -6
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db.egg-info/SOURCES.txt +1 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/pyproject.toml +1 -1
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/setup.py +1 -1
- ic_python_db-0.11.0/ic_python_db/constants.py +0 -6
- ic_python_db-0.11.0/ic_python_db/db_engine.py +0 -557
- ic_python_db-0.11.0/ic_python_db/storage.py +0 -69
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/LICENSE +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/MANIFEST.in +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/_cdk.py +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/context.py +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/hooks.py +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/mixins.py +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/py.typed +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/schema.py +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db/system_time.py +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db.egg-info/dependency_links.txt +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/ic_python_db.egg-info/top_level.txt +0 -0
- {ic_python_db-0.11.0 → ic_python_db-0.13.0}/requirements-dev.txt +0 -0
- {ic_python_db-0.11.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,6 +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**: `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()`).
|
|
63
64
|
- **Entity-Relational Database**: Create, read and write entities with OneToOne, OneToMany, ManyToOne, and ManyToMany relationships.
|
|
64
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.
|
|
65
66
|
- **Entity Hooks**: Intercept and control entity lifecycle events (create, modify, delete) with `on_event` hooks.
|
|
@@ -162,11 +163,12 @@ Then use the defined entities to store objects:
|
|
|
162
163
|
|
|
163
164
|
assert Person.count() == 3
|
|
164
165
|
assert Person.max_id() == 4
|
|
165
|
-
assert Person.instances() == [john, alice, eva]
|
|
166
166
|
|
|
167
|
-
# Cursor-based pagination
|
|
168
|
-
|
|
169
|
-
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
|
|
170
172
|
|
|
171
173
|
# Retrieve database contents in JSON format
|
|
172
174
|
print(Database.get_instance().dump_json(pretty=True))
|
|
@@ -228,7 +230,29 @@ while cursor is not None:
|
|
|
228
230
|
|
|
229
231
|
The rebuild is idempotent. The `indexed` flag is not part of the schema descriptor, so toggling it never triggers upgrade-compatibility errors.
|
|
230
232
|
|
|
231
|
-
|
|
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.
|
|
232
256
|
|
|
233
257
|
## Namespaces
|
|
234
258
|
|
|
@@ -11,6 +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**: `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()`).
|
|
14
15
|
- **Entity-Relational Database**: Create, read and write entities with OneToOne, OneToMany, ManyToOne, and ManyToMany relationships.
|
|
15
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.
|
|
16
17
|
- **Entity Hooks**: Intercept and control entity lifecycle events (create, modify, delete) with `on_event` hooks.
|
|
@@ -113,11 +114,12 @@ Then use the defined entities to store objects:
|
|
|
113
114
|
|
|
114
115
|
assert Person.count() == 3
|
|
115
116
|
assert Person.max_id() == 4
|
|
116
|
-
assert Person.instances() == [john, alice, eva]
|
|
117
117
|
|
|
118
|
-
# Cursor-based pagination
|
|
119
|
-
|
|
120
|
-
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
|
|
121
123
|
|
|
122
124
|
# Retrieve database contents in JSON format
|
|
123
125
|
print(Database.get_instance().dump_json(pretty=True))
|
|
@@ -179,7 +181,29 @@ while cursor is not None:
|
|
|
179
181
|
|
|
180
182
|
The rebuild is idempotent. The `indexed` flag is not part of the schema descriptor, so toggling it never triggers upgrade-compatibility errors.
|
|
181
183
|
|
|
182
|
-
|
|
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.
|
|
183
207
|
|
|
184
208
|
## Namespaces
|
|
185
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
|