Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion python_tests/test_find.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .conftest import DB0_DIR, DATA_PX
import itertools
from datetime import datetime
import operator


@db0.memo()
Expand Down Expand Up @@ -354,4 +355,24 @@ def test_find_with_scope_defined(db0_fixture):
# use scoped find to find instances from different prefixes
assert [x.value for x in db0.find(MemoScopedClass, "tag1", prefix = px_name)] == [123]
assert [x.value for x in db0.find(MemoScopedClass, "tag1", prefix = other_px_name)] == [456]



def test_find_extract_multiple_indices(db0_no_autocommit, memo_tags):
# pick elements by specific indexes
result = db0.find("tag1")[3, 6, 7]
assert type(result) is tuple
assert len(result) == 3


def test_find_extract_duplicate_indices(db0_no_autocommit, memo_tags):
# pick elements by specific indexes
result = db0.find("tag1")[3, 6, 7, 3, 6]
assert result[0] is result[3]
assert result[1] is result[4]


def test_find_extract_invalid_indices(db0_no_autocommit, memo_tags):
query = db0.find("tag1")
assert len(query) == 10
with pytest.raises(IndexError):
_ = db0.find("tag1")[3, 6, 7, 3, 10]
59 changes: 56 additions & 3 deletions python_tests/test_memo_no_cache.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from random import random
import pytest
import dbzero as db0
from .memo_test_types import MemoTestClass, TriColor, MemoAnyAttrs
from .memo_test_types import MemoTestClass
from dataclasses import dataclass
from .conftest import DB0_DIR
import random
import string
import gc
Expand All @@ -17,8 +18,10 @@ def rand_string(max_len):
@dataclass
class MemoNoCacheClass:
data: str

value: int = 0

def __init__(self):
self.value = random.randint(0, 1000000)
self.data = rand_string(12 << 10)


Expand Down Expand Up @@ -61,4 +64,54 @@ def test_excluding_no_cache_instances_from_dbzero_cache(db0_fixture):
final_cache_size = db0.get_cache_stats()["size"]
# make sure cache utilization is low
assert abs(final_cache_size - initial_cache_size) < (300 << 10)



def test_fetching_no_cache_objects(db0_fixture):
px_name = db0.get_current_prefix().name
buf = db0.list()
uuid_list = []
for _ in range(100):
obj = MemoNoCacheClass()
buf.append(obj)
uuid_list.append(db0.uuid(obj))

db0.close()
db0.init(DB0_DIR)
db0.open(px_name, "r")

# now fetch objects by uuid
initial_cache_size = db0.get_cache_stats()["size"]
total_len = 0
for id in uuid_list:
# NOTE: must fetch with type, otherwise no_cache flag may not be honored
obj = db0.fetch(MemoNoCacheClass, id)
# this forces data retrieval
total_len += len(obj.data)

final_cache_size = db0.get_cache_stats()["size"]
# make sure cache utilization is low
assert abs(final_cache_size - initial_cache_size) < (300 << 10)


def test_find_no_cache_objects(db0_fixture):
px_name = db0.get_current_prefix().name
buf = db0.list()
for _ in range(100):
obj = MemoNoCacheClass()
buf.append(obj)

db0.close()
db0.init(DB0_DIR)
db0.open(px_name, "r")

# now retrieve objects using db0.find
initial_cache_size = db0.get_cache_stats()["size"]
total_len = 0
for obj in db0.find(MemoNoCacheClass):
# this forces data retrieval (but not caching)
total_len += len(obj.data)

assert total_len > 0
final_cache_size = db0.get_cache_stats()["size"]
# make sure cache utilization is low
assert abs(final_cache_size - initial_cache_size) < (300 << 10)
4 changes: 2 additions & 2 deletions src/dbzero/bindings/python/PyAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ namespace db0::python
PY_API_FUNC
return runSafe(tryGetCacheStats);
}

PyObject *tryGetLangCacheStats()
{
auto lang_cache = PyToolkit::getPyWorkspace().getWorkspace().getLangCache();
Expand Down Expand Up @@ -144,7 +144,7 @@ namespace db0::python
return NULL;
}

PY_API_FUNC
PY_API_FUNC
return runSafe(tryFetch, py_id, reinterpret_cast<PyTypeObject*>(py_type), prefix_name).steal();
}

Expand Down
21 changes: 13 additions & 8 deletions src/dbzero/bindings/python/PyInternalAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,27 +170,32 @@ namespace db0::python
using ClassFactory = db0::object_model::ClassFactory;
using Class = db0::object_model::Class;

// validate pre-storage class first
// Validate pre-storage class first
if (py_expected_type && !checkObjectIdType(object_id, py_expected_type)) {
THROWF(db0::InputException) << "Object ID type mismatch";
}

auto storage_class = object_id.m_storage_class;
auto addr = object_id.m_address;
if (storage_class == db0::object_model::StorageClass::OBJECT_REF) {
if (storage_class == db0::object_model::StorageClass::OBJECT_REF) {
auto &class_factory = db0::object_model::getClassFactory(*fixture);
auto result = PyToolkit::unloadObject(fixture, addr, class_factory, nullptr, addr.getInstanceId());
auto &memo = reinterpret_cast<MemoObject*>(result.get())->ext();

// validate type if requested (no validation for MemoBase)
if (py_expected_type && !PyToolkit::getTypeManager().isMemoBase(py_expected_type)) {
if (py_expected_type && !PyToolkit::getTypeManager().isMemoBase(py_expected_type)) {
// in other cases the type must match the actual object type
auto expected_class = class_factory.getExistingType(py_expected_type);
auto expected_class = class_factory.getExistingType(py_expected_type);
// honor class-specific access flags (e.g. type-level no_cache)
auto result = PyToolkit::unloadObject(fixture, addr, class_factory, nullptr, addr.getInstanceId(),
expected_class->getInstanceFlags()
);
auto &memo = reinterpret_cast<MemoObject*>(result.get())->ext();
if (memo.getType() != *expected_class) {
THROWF(db0::InputException) << "Object type mismatch";
}
return result;
} else {
// unload without type validation
return PyToolkit::unloadObject(fixture, addr, class_factory, nullptr, addr.getInstanceId());
}
return result;
} else if (storage_class == db0::object_model::StorageClass::DB0_CLASS) {
auto &class_factory = db0::object_model::getClassFactory(*fixture);
auto class_ptr = class_factory.getTypeByAddr(addr).m_class;
Expand Down
3 changes: 3 additions & 0 deletions src/dbzero/bindings/python/PyInternalAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ namespace db0::python
} catch (const db0::ClassNotFoundException &e) {
PyErr_SetString(PyToolkit::getTypeManager().getClassNotFoundError(), e.what());
return returnError();
} catch (const db0::IndexException &e) {
PyErr_SetString(PyExc_IndexError, e.what());
return returnError();
}
#if ENABLE_DEBUG_EXCEPTIONS
catch (const db0::AbstractException &e) {
Expand Down
27 changes: 13 additions & 14 deletions src/dbzero/bindings/python/PyTagsAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,8 @@ namespace db0::python
return { std::move(result.first), std::move(query_observers) };
}

PyObject *trySplitBy(PyObject *args, PyObject *kwargs)
PyObject *trySplitBy(PyObject *py_tags, PyObject *py_query, bool exclusive)
{
// extract 2 object arguments
PyObject *py_tags = nullptr;
PyObject *py_query = nullptr;
int exclusive = true;
// tags, query, exclusive (bool)
static const char *kwlist[] = {"tags", "query", "exclusive", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|p", const_cast<char**>(kwlist), &py_tags, &py_query, &exclusive)) {
PyErr_SetString(PyExc_TypeError, "Invalid argument type");
return NULL;
}

if (!PyObjectIterable_Check(py_query)) {
THROWF(db0::InputException) << "Invalid argument type";
}
Expand All @@ -84,9 +73,19 @@ namespace db0::python

PyObject *PyAPI_splitBy(PyObject *, PyObject *args, PyObject *kwargs)
{
// extract 2 object arguments
PyObject *py_tags = nullptr;
PyObject *py_query = nullptr;
int exclusive = true;
// tags, query, exclusive (bool)
static const char *kwlist[] = {"tags", "query", "exclusive", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|p", const_cast<char**>(kwlist), &py_tags, &py_query, &exclusive)) {
return NULL;
}

PY_API_FUNC
return runSafe(trySplitBy, args, kwargs);
}
return runSafe(trySplitBy, py_tags, py_query, exclusive);
}

PyObject *trySelectModCandidates(const ObjectIterable &iterable, StateNumType from_state,
std::optional<StateNumType> to_state)
Expand Down
2 changes: 1 addition & 1 deletion src/dbzero/bindings/python/PyTagsAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace db0::python
const char *prefix_name = nullptr);

PyObject *PyAPI_splitBy(PyObject *, PyObject *args, PyObject *kwargs);

PyObject *PyAPI_selectModCandidates(PyObject *, PyObject *args, PyObject *kwargs);

PyObject *PyAPI_splitBySnapshots(PyObject *, PyObject *const *args, Py_ssize_t nargs);
Expand Down
Loading
Loading