Added DataServicesManager and DataService

This commit is contained in:
2026-02-27 21:10:11 +01:00
parent efbc5a59ff
commit 0a766581ed
16 changed files with 1465 additions and 126 deletions

View File

@@ -190,8 +190,9 @@ class TestDataGridsManagerBehaviour:
doc = datagrid_manager._state.elements[0]
# Verify DataGrid is registered
tables = datagrid_manager._registry.get_all_tables()
assert "Untitled.Sheet1" in tables, "DataGrid should be registered as Untitled.Sheet1"
entries = datagrid_manager._registry.get_all_entries()
assert doc.datagrid_id in entries, "DataGrid should be registered by grid_id"
assert entries[doc.datagrid_id] == ("Untitled", "Sheet1"), "Registry entry should match namespace and name"
# Verify DataGrid exists in InstancesManager
from myfasthtml.core.instances import InstancesManager

View File

View File

@@ -0,0 +1,44 @@
import shutil
import pytest
from dbengine.handlers import handlers
from myfasthtml.core.data.DataServicesManager import DataServicesManager
from myfasthtml.core.dbengine_utils import DataFrameHandler
from myfasthtml.core.dbmanager import DbManager
from myfasthtml.core.instances import SingleInstance, InstancesManager
@pytest.fixture(scope="session")
def session():
handlers.register_handler(DataFrameHandler())
return {
"user_info": {
"id": "test_tenant_id",
"email": "test@email.com",
"username": "test user",
"role": [],
}
}
@pytest.fixture
def parent(session):
instance = SingleInstance(session=session, _id="test_parent_id")
return instance
@pytest.fixture
def db_manager(parent):
shutil.rmtree("TestDb", ignore_errors=True)
db_manager_instance = DbManager(parent, root="TestDb", auto_register=True)
yield db_manager_instance
shutil.rmtree("TestDb", ignore_errors=True)
InstancesManager.reset()
@pytest.fixture
def dsm(parent, db_manager):
return DataServicesManager(parent, parent._session)

View File

@@ -0,0 +1,213 @@
"""Unit tests for DataService."""
import pandas as pd
import pytest
from myfasthtml.core.constants import ColumnType
from myfasthtml.core.data.ColumnDefinition import ColumnDefinition
class TestDataInitialisation:
"""Tests for the Data initialisation section of DataService."""
@pytest.fixture
def service(self, dsm):
return dsm.create_service("ns.tbl", save_state=False)
def test_i_can_load_a_dataframe(self, service):
"""load_dataframe() populates the store and column definitions."""
df = pd.DataFrame({"name": ["Alice", "Bob"], "age": [30, 25]})
service.load_dataframe(df)
assert service.get_store().ne_df is not None
assert service.get_store().ns_total_rows == 2
assert len(service.columns) == 2
def test_i_can_load_an_empty_dataframe(self, service):
"""load_dataframe() with empty DataFrame sets total_rows to 0."""
service.load_dataframe(pd.DataFrame())
assert service.get_store().ns_total_rows == 0
assert service.columns == []
def test_i_can_load_dataframe_without_reinitializing_columns(self, service):
"""load_dataframe(init_columns=False) preserves existing column definitions."""
df = pd.DataFrame({"a": [1]})
service.load_dataframe(df)
original_columns = list(service.columns)
df2 = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
service.load_dataframe(df2, init_columns=False)
assert service.columns == original_columns
def test_i_can_load_none_dataframe_without_error(self, service):
"""load_dataframe(None) is a no-op and does not raise.
Why this matters:
- Early return on None protects against uninitialized callers.
- ne_df must remain None (no side effects on the store).
"""
service.load_dataframe(None)
assert service.get_store().ne_df is None
def test_i_can_load_dataframe_with_column_name_normalization(self, service):
"""load_dataframe() normalizes column names to safe IDs via make_safe_id.
Why this matters:
- Columns with spaces or special characters must be accessible as safe IDs.
- make_safe_id lowercases and replaces non-safe characters with underscores.
"""
df = pd.DataFrame({"First Name": ["Alice"], "Last Name": ["Smith"]})
service.load_dataframe(df)
col_ids = [c.col_id for c in service.columns]
assert col_ids == ["first_name", "last_name"]
class TestMutations:
"""Tests for the Mutations section of DataService."""
@pytest.fixture
def service(self, dsm):
svc = dsm.create_service("ns.mutations", save_state=False)
svc.load_dataframe(pd.DataFrame({"value": [1, 2, 3]}))
return svc
def test_i_can_add_a_row(self, service):
"""add_row() appends a row with default values and updates the caches."""
service.add_row()
assert service.get_store().ns_total_rows == 4
assert len(service.get_store().ne_df) == 4
def test_i_can_add_a_row_with_custom_data(self, service):
"""add_row() with explicit data stores the provided values."""
service.add_row(row_data={"value": 99})
assert service.get_store().ne_df.iloc[-1]["value"] == 99
def test_i_can_set_data(self, service):
"""set_data() updates the cell in the DataFrame, fast-access cache, and row data."""
service.set_data("value", 1, 99)
assert service.get_store().ne_df.at[1, "value"] == 99
assert service.get_store().ns_fast_access["value"][1] == 99
assert service.get_store().ns_row_data[1]["value"] == 99
@pytest.mark.parametrize("col_type, expected_default", [
(ColumnType.Text, ""),
(ColumnType.Number, 0),
(ColumnType.Bool, False),
(ColumnType.Datetime, pd.NaT),
(ColumnType.Choice, ""),
(ColumnType.Enum, ""),
(ColumnType.RowSelection_, ""),
])
def test_i_can_add_column_with_correct_default_value(self, service, col_type, expected_default):
"""add_column() creates a DataFrame column with the type-appropriate default value.
Why these assertions matter:
- col_id in ne_df.columns: Confirms the column is materialized in the DataFrame.
- len(columns) == 2: Confirms the column is registered in the metadata.
- default value: Each type has a specific sentinel value; wrong defaults corrupt data.
- pd.isna() for Datetime: pd.NaT does not support equality comparison.
"""
col_def = ColumnDefinition(col_id="__new__", col_index=-1, title="New Col", type=col_type)
service.add_column(col_def)
assert col_def.col_id in service.get_store().ne_df.columns
assert len(service.columns) == 2
actual = service.get_store().ne_df[col_def.col_id].iloc[0]
if pd.isna(expected_default):
assert pd.isna(actual)
else:
assert actual == expected_default
@pytest.mark.parametrize("col_type", [ColumnType.Formula, ColumnType.RowIndex])
def test_i_can_add_virtual_column_without_dataframe_column(self, service, col_type):
"""add_column() with virtual types does not create a DataFrame column.
Why these assertions matter:
- col_id not in ne_df.columns: Virtual columns are computed, not stored in the DataFrame.
- col_index == -1: Sentinel value marking virtual columns.
- len(columns) == 2: Column is registered in the state metadata despite being virtual.
"""
col_def = ColumnDefinition(col_id="__new__", col_index=-1, title="Virtual", type=col_type)
service.add_column(col_def)
assert col_def.col_id not in service.get_store().ne_df.columns
assert col_def.col_index == -1
assert len(service.columns) == 2
def test_i_can_add_row_without_loaded_dataframe_without_error(self, dsm):
"""add_row() is a no-op and does not raise when no DataFrame is loaded."""
service = dsm.create_service("ns.nodf_row", save_state=False)
service.add_row()
assert service.get_store().ne_df is None
def test_i_can_set_data_without_loaded_dataframe_without_error(self, dsm):
"""set_data() is a no-op and does not raise when no DataFrame is loaded."""
service = dsm.create_service("ns.nodf_set", save_state=False)
service.set_data("x", 0, 42)
assert service.get_store().ne_df is None
class TestFormulaManagement:
"""Tests for the Formula management section of DataService."""
@pytest.fixture
def service(self, dsm):
svc = dsm.create_service("ns.formula", save_state=False)
svc.load_dataframe(pd.DataFrame({"a": [1, 2, 3]}))
return svc
def test_i_can_get_table_name(self, service):
"""table_name property returns the value set at creation."""
assert service.table_name == "ns.formula"
def test_i_can_update_table_name(self, service):
"""set_table_name() updates the table name."""
service.set_table_name("ns.new_name")
assert service.table_name == "ns.new_name"
def test_i_can_register_formula(self, service):
"""register_formula() registers a formula in the shared FormulaEngine.
Why these assertions matter:
- has_formula: Confirms the formula was registered in the engine's DAG.
- get_formula_text: Confirms the source expression is stored as-is.
"""
service.register_formula("computed", "{a} + 1")
engine = service.get_formula_engine()
assert engine.has_formula("ns.formula", "computed")
assert engine.get_formula_text("ns.formula", "computed") == "{a} + 1"
def test_i_can_remove_formula(self, service):
"""remove_formula() unregisters a formula from the FormulaEngine."""
service.register_formula("computed", "{a} + 1")
service.remove_formula("computed")
engine = service.get_formula_engine()
assert not engine.has_formula("ns.formula", "computed")
def test_i_cannot_register_invalid_formula(self, service):
"""register_formula() with invalid DSL syntax does not register the formula.
Why this matters:
- parse_formula() raises DSLSyntaxError when it cannot parse the expression.
- register_formula() catches the exception to protect the caller, but the
formula must remain absent from the engine — not silently removed.
"""
service.register_formula("computed", "invalid syntax without braces")
engine = service.get_formula_engine()
assert not engine.has_formula("ns.formula", "computed")

View File

@@ -0,0 +1,210 @@
"""Integration tests for DataService formula evaluation through DataServicesManager.
These tests exercise the full stack: DataServicesManager owns the FormulaEngine
and provides the registry_resolver that enables cross-table formula resolution.
Each test uses real DataService instances and real DataStore objects — no fakes.
"""
import pytest
import pandas as pd
from myfasthtml.core.constants import ColumnType
from myfasthtml.core.data.ColumnDefinition import ColumnDefinition
class TestIntraTableFormula:
"""Single-table formula evaluation through the DSM/DataService stack."""
@pytest.fixture
def service(self, dsm):
svc = dsm.create_service("ns.sales", save_state=False)
svc.load_dataframe(pd.DataFrame({"price": [10, 20, 30], "qty": [2, 3, 4]}))
return svc
def test_i_can_evaluate_formula_on_single_table(self, service):
"""register_formula() + ensure_ready() computes the column for all rows.
Why these assertions matter:
- ns_fast_access["total"]: ensure_ready() writes results to the cache used by rendering.
- All three rows: verifies the formula is applied to every row, not just the first.
"""
service.register_formula("total", "{price} * {qty}")
service.ensure_ready()
result = service.get_store().ns_fast_access["total"]
assert result[0] == 20
assert result[1] == 60
assert result[2] == 120
def test_i_can_reevaluate_formula_after_data_change(self, service):
"""set_data() marks dependent formula columns dirty; ensure_ready() recomputes them.
Why these assertions matter:
- result[0] updated: confirms the dirty flag propagated and row 0 was recomputed.
- result[1] unchanged: confirms only affected rows are recomputed (no unnecessary work).
"""
service.register_formula("total", "{price} * {qty}")
service.ensure_ready()
service.set_data("price", 0, 100)
service.ensure_ready()
result = service.get_store().ns_fast_access["total"]
assert result[0] == 200
assert result[1] == 60
class TestCrossTableFormula:
"""Cross-table formula resolution via DataServicesManager.registry_resolver.
Table names use namespace notation (e.g. "ns.products"). The DSL grammar
now supports multiple dots in TABLE_COL_REF; the transformer splits on the
last dot to separate the table name from the column name.
"""
@pytest.fixture
def orders_service(self, dsm):
svc = dsm.create_service("ns.orders", save_state=False)
svc.load_dataframe(pd.DataFrame({"qty": [2, 3]}))
return svc
@pytest.fixture
def products_service(self, dsm):
svc = dsm.create_service("ns.products", save_state=False)
svc.load_dataframe(pd.DataFrame({"price": [10, 20]}))
return svc
def test_i_can_evaluate_cross_table_formula(self, orders_service, products_service):
"""A formula in one service can reference a namespaced table from another service.
Why these assertions matter:
- result[0] and result[1]: confirms the registry_resolver resolved "ns.products"
correctly and combined its data with the orders data row by row.
"""
orders_service.register_formula("total", "{ns.products.price} * {qty}")
orders_service.ensure_ready()
result = orders_service.get_store().ns_fast_access["total"]
assert result[0] == 20
assert result[1] == 60
def test_i_cannot_resolve_cross_table_formula_for_unknown_table(self, orders_service):
"""A formula referencing an unregistered table resolves to None without raising.
Why this matters:
- result is None: confirms the engine degrades gracefully when the resolver
returns None, instead of raising or producing corrupt values.
"""
orders_service.register_formula("total", "{ns.unknown_table.price} * {qty}")
orders_service.ensure_ready()
result = orders_service.get_store().ns_fast_access["total"]
assert result[0] is None
assert result[1] is None
class TestCrossTableFormulaWhere:
"""Cross-table formula resolution using an explicit WHERE clause.
The WHERE clause scans the remote table for a row where remote_column == local_value,
enabling correct lookups regardless of row ordering between tables.
"""
@pytest.fixture
def orders_service(self, dsm):
svc = dsm.create_service("ns.orders", save_state=False)
svc.load_dataframe(pd.DataFrame({"product_id": [2, 1], "qty": [3, 5]}))
return svc
@pytest.fixture
def products_service(self, dsm):
svc = dsm.create_service("ns.products", save_state=False)
svc.load_dataframe(pd.DataFrame({"product_id": [1, 2], "price": [10, 20]}))
return svc
def test_i_can_lookup_value_with_where_clause_non_sequential(self, orders_service, products_service):
"""WHERE resolves the correct remote row even when tables are not aligned by position.
Why these assertions matter:
- result[0] == 60: order row 0 has product_id=2, products row 1 has price=20 → 20*3=60.
- result[1] == 50: order row 1 has product_id=1, products row 0 has price=10 → 10*5=50.
Row-index fallback would return 10*3=30 and 20*5=100 — both wrong.
"""
orders_service.register_formula(
"total",
"{ns.products.price where ns.products.product_id = product_id} * {qty}"
)
orders_service.ensure_ready()
result = orders_service.get_store().ns_fast_access["total"]
assert result[0] == 60
assert result[1] == 50
def test_i_can_lookup_returns_none_when_no_match(self, orders_service, products_service):
"""WHERE returns None when the local value has no matching row in the remote table.
Why this matters:
- result[0] is None: product_id=2 exists in products, but product_id=99 does not.
- No exception is raised: the engine must degrade gracefully on missing lookups.
"""
orders_service_no_match = orders_service
orders_service_no_match.get_store().ns_fast_access["product_id"][0] = 99
orders_service_no_match.register_formula(
"total",
"{ns.products.price where ns.products.product_id = product_id} * {qty}"
)
orders_service_no_match.ensure_ready()
result = orders_service_no_match.get_store().ns_fast_access["total"]
assert result[0] is None
class TestFormulaLifecycle:
"""End-to-end formula lifecycle: column creation, registration, and evaluation."""
@pytest.fixture
def service(self, dsm):
svc = dsm.create_service("ns.lifecycle", save_state=False)
svc.load_dataframe(pd.DataFrame({"a": [1, 2, 3]}))
return svc
def test_i_can_add_formula_column_and_evaluate(self, service):
"""add_column(Formula) + register_formula() + ensure_ready() produces computed values.
Why these assertions matter:
- col_id in ns_fast_access: ensure_ready() must write the formula column into the cache.
- Values [2, 4, 6]: validates the formula expression is correctly applied to all rows.
"""
col_def = ColumnDefinition(col_id="__new__", col_index=-1,
title="Doubled", type=ColumnType.Formula,
formula="{a} * 2")
service.add_column(col_def)
service.register_formula(col_def.col_id, col_def.formula)
service.ensure_ready()
result = service.get_store().ns_fast_access[col_def.col_id]
assert list(result) == [2, 4, 6]
def test_i_can_evaluate_formula_after_adding_row(self, service):
"""add_row() marks formula columns dirty; ensure_ready() computes the new row.
Why these assertions matter:
- len(result) == 4: confirms the new row was appended and the cache extended.
- result[3] == 20: confirms the formula was recalculated for the new row (a=10, * 2).
- result[0] == 2: confirms existing rows are not corrupted by the recalculation.
"""
col_def = ColumnDefinition(col_id="__new__", col_index=-1,
title="Doubled", type=ColumnType.Formula,
formula="{a} * 2")
service.add_column(col_def)
service.register_formula(col_def.col_id, col_def.formula)
service.ensure_ready()
service.add_row(row_data={"a": 10})
service.ensure_ready()
result = service.get_store().ns_fast_access[col_def.col_id]
assert len(result) == 4
assert result[0] == 2
assert result[3] == 20

View File

@@ -0,0 +1,121 @@
"""Unit tests for DataServicesManager."""
import pandas as pd
class TestDataServicesManagerServiceLifecycle:
def test_i_can_create_a_service(self, dsm):
"""create_service() returns a DataService accessible by grid_id."""
service = dsm.create_service("ns.tbl", save_state=False)
assert service is not None
assert service.get_id() is not None
assert dsm.get_service(service.get_id()) is service
def test_i_can_create_service_with_correct_table_name(self, dsm):
"""create_service() sets the table_name on the returned DataService.
create_service() calls service.set_table_name() internally.
This test verifies the side effect is applied before returning the service.
"""
service = dsm.create_service("ns.my_table", save_state=False)
assert service.table_name == "ns.my_table"
def test_i_can_create_service_forcing_the_id(self, dsm):
"""create_service() sets the table_name on the returned DataService.
create_service() calls service.set_table_name() internally.
This test verifies the side effect is applied before returning the service.
"""
service = dsm.create_service("ns.my_table", _id="grid_id", save_state=False)
assert service.get_id() == "grid_id"
def test_i_can_get_a_service_by_grid_id(self, dsm):
"""get_service() returns the correct service."""
svc1 = dsm.create_service("ns.t1", _id="g1", save_state=False)
svc2 = dsm.create_service("ns.t2", _id="g2", save_state=False)
assert dsm.get_service("g1") is svc1
assert dsm.get_service("g2") is svc2
def test_i_cannot_get_a_nonexistent_service(self, dsm):
"""get_service() returns None for unknown grid_id."""
assert dsm.get_service("does_not_exist") is None
def test_i_can_remove_a_service(self, dsm):
"""remove_service() unregisters the service."""
service = dsm.create_service("ns.rm", save_state=False)
dsm.remove_service(service.get_id())
assert dsm.get_service(service.get_id()) is None
def test_i_can_remove_a_nonexistent_service_without_error(self, dsm):
"""remove_service() on unknown grid_id does not raise."""
dsm.remove_service("ghost") # should not raise
def test_i_can_restore_a_service(self, dsm):
"""restore_service() creates and registers a service if not already present."""
service = dsm.restore_service("grid_restore")
assert service is not None
assert dsm.get_service("grid_restore") is service
assert service.get_id() == "grid_restore"
def test_i_can_restore_existing_service(self, dsm):
"""restore_service() returns the existing service when already registered."""
original = dsm.create_service("ns.e", _id="grid_exist", save_state=False)
restored = dsm.restore_service("grid_exist")
assert restored is original
class TestDataServicesManagerFormulaEngine:
def test_i_can_get_formula_engine(self, dsm):
"""get_formula_engine() returns the shared FormulaEngine instance."""
engine = dsm.get_formula_engine()
assert engine is not None
def test_i_can_verify_shared_formula_engine(self, dsm):
"""All services share the same FormulaEngine from DataServicesManager."""
svc1 = dsm.create_service("ns.fe1", save_state=False)
svc2 = dsm.create_service("ns.fe2", save_state=False)
assert svc1.get_formula_engine() is svc2.get_formula_engine()
assert svc1.get_formula_engine() is dsm.get_formula_engine()
def test_i_can_resolve_store_by_table_name(self, dsm):
"""FormulaEngine resolver finds the DataStore for a given table name."""
service = dsm.create_service("ns.resolver", save_state=False)
df = pd.DataFrame({"a": [1, 2]})
service.load_dataframe(df)
store = dsm._resolve_store_for_table("ns.resolver")
assert store is service.get_store()
def test_i_can_resolve_correct_store_among_multiple_services(self, dsm):
"""_resolve_store_for_table() identifies the right store when multiple services are registered.
The resolver iterates over all registered services and must return the store
whose service has a matching table_name, not another service's store.
"""
svc_a = dsm.create_service("ns.table_a", save_state=False)
svc_b = dsm.create_service("ns.table_b", save_state=False)
df = pd.DataFrame({"x": [10, 20]})
svc_a.load_dataframe(df)
svc_b.load_dataframe(df.copy())
store_a = dsm._resolve_store_for_table("ns.table_a")
store_b = dsm._resolve_store_for_table("ns.table_b")
assert store_a is svc_a.get_store()
assert store_b is svc_b.get_store()
assert store_a is not store_b
def test_i_cannot_resolve_unknown_table(self, dsm):
"""FormulaEngine resolver returns None for an unknown table name."""
result = dsm._resolve_store_for_table("unknown.table")
assert result is None

View File

@@ -2,9 +2,7 @@ import shutil
import pytest
from dbengine.handlers import handlers
from pandas import DataFrame
from myfasthtml.controls.DataGrid import DataGrid, DatagridConf
from myfasthtml.core.DataGridsRegistry import DataGridsRegistry, DATAGRIDS_REGISTRY_ENTRY_KEY
from myfasthtml.core.dbengine_utils import DataFrameHandler
from myfasthtml.core.dbmanager import DbManager
@@ -31,7 +29,6 @@ def session():
@pytest.fixture
def parent(session):
instance = SingleInstance(session=session, _id="test_parent_id")
instance.get_formula_engine = lambda: None
return instance
@@ -39,76 +36,60 @@ def parent(session):
def db_manager(parent):
shutil.rmtree("TestDb", ignore_errors=True)
db_manager_instance = DbManager(parent, root="TestDb", auto_register=True)
yield db_manager_instance
shutil.rmtree("TestDb", ignore_errors=True)
InstancesManager.reset()
@pytest.fixture
def dg(parent):
# the table must be created
data = {"name": ["john", "jane"], "id": [1, 2]}
df = DataFrame(data)
dgc = DatagridConf("namespace", "table_name")
datagrid = DataGrid(parent, conf=dgc, save_state=True)
datagrid.init_from_dataframe(df, init_state=True)
yield datagrid
datagrid.dispose()
@pytest.fixture
def dgr(parent, db_manager):
return DataGridsRegistry(parent)
def test_entry_is_created_at_startup(db_manager, dgr, ):
def test_i_can_create_registry_with_empty_state(db_manager, dgr):
"""Registry is initialised with an empty dict in DB."""
assert db_manager.exists_entry(DATAGRIDS_REGISTRY_ENTRY_KEY)
assert clean_db_object(db_manager.load(DATAGRIDS_REGISTRY_ENTRY_KEY)) == {}
def test_i_can_put_a_table_in_registry(dgr):
def test_i_can_put_and_retrieve_entries(dgr):
"""put() persists entries retrievable via get_all_entries()."""
dgr.put("namespace", "name", "datagrid_id")
dgr.put("namespace2", "name2", "datagrid_id2")
assert dgr.get_all_tables() == ["namespace.name", "namespace2.name2"]
entries = dgr.get_all_entries()
assert "datagrid_id" in entries
assert "datagrid_id2" in entries
assert entries["datagrid_id"] == ("namespace", "name")
assert entries["datagrid_id2"] == ("namespace2", "name2")
def test_i_can_columns_names_for_a_table(dgr, dg):
expected = ["__row_index__", "name", "id"] if dg.get_state().row_index else ["name", "id"]
namespace, name = dg.get_settings().namespace, dg.get_settings().name
dgr.put(namespace, name, dg.get_id())
table_full_name = f"{namespace}.{name}"
assert dgr.get_columns(table_full_name) == expected
def test_i_can_remove_an_entry(dgr):
"""remove() deletes the entry from the registry."""
dgr.put("ns", "tbl", "grid_1")
assert "grid_1" in dgr.get_all_entries()
dgr.remove("grid_1")
assert "grid_1" not in dgr.get_all_entries()
def test_i_can_get_columns_values(dgr, dg):
namespace, name = dg.get_settings().namespace, dg.get_settings().name
dgr.put(namespace, name, dg.get_id())
table_full_name = f"{namespace}.{name}"
assert dgr.get_column_values(table_full_name, "name") == ["john", "jane"]
def test_i_can_remove_nonexistent_entry_without_error(dgr):
"""remove() on a missing id does not raise."""
dgr.remove("does_not_exist") # should not raise
def test_i_can_get_row_count(dgr, dg):
namespace, name = dg.get_settings().namespace, dg.get_settings().name
dgr.put(namespace, name, dg.get_id())
table_full_name = f"{namespace}.{name}"
assert dgr.get_row_count(table_full_name) == 2
def test_i_can_put_multiple_entries_and_get_all(dgr):
"""get_all_entries() returns all registered grids."""
dgr.put("ns1", "t1", "id1")
dgr.put("ns2", "t2", "id2")
dgr.put("ns3", "t3", "id3")
entries = dgr.get_all_entries()
assert len(entries) == 3
def test_i_can_manage_when_table_name_does_not_exist(dgr):
assert dgr.get_columns("namespace.name") == []
assert dgr.get_row_count("namespace.name") == 0
def test_i_can_manage_when_column_does_not_exist(dgr, dg):
namespace, name = dg.get_settings().namespace, dg.get_settings().name
dgr.put(namespace, name, dg.get_id())
table_full_name = f"{namespace}.{name}"
assert len(dgr.get_columns(table_full_name)) > 0
assert dgr.get_column_values("namespace.name", "") == []
def test_i_can_get_empty_entries_when_registry_is_empty(dgr):
"""get_all_entries() returns empty dict when nothing registered."""
assert dgr.get_all_entries() == {}