diff --git a/python/grass/temporal/core.py b/python/grass/temporal/core.py index 2789224fecb..ceddbe11dbc 100644 --- a/python/grass/temporal/core.py +++ b/python/grass/temporal/core.py @@ -638,6 +638,10 @@ def init( c_library_interface.stop() c_library_interface = None + # Set defaults for the TGIS DB (overwritten later if initialized) + tgis_backend = "sqlite" + tgis_database_string = None + # Set the global variable for faster access current_mapset = new_mapset current_location = new_location diff --git a/python/grass/temporal/metadata.py b/python/grass/temporal/metadata.py index d704db1de8a..580cf7f5d95 100644 --- a/python/grass/temporal/metadata.py +++ b/python/grass/temporal/metadata.py @@ -23,7 +23,12 @@ """ from .base import SQLDatabaseInterface -from .core import SQLDatabaseInterfaceConnection, get_tgis_db_version_from_metadata +from .core import ( + SQLDatabaseInterfaceConnection, + get_tgis_database_string, + get_tgis_db_version, + get_tgis_db_version_from_metadata, +) ############################################################################### @@ -1457,7 +1462,15 @@ def __init__( self, "strds_metadata", ident, title, description ) - if get_tgis_db_version_from_metadata() > 2: + # TGIS metadata is only available with an accessible temporal database + # initialized with tgis.init(). Initialization with tgis.init(skip_db_init=True) + # will result in an empty tgis_database_string is empty, fall back to a global + # tgis_db_version if tgis is not fully initialized. + if get_tgis_database_string() is not None: + tgis_db_version_ = get_tgis_db_version_from_metadata() + else: + tgis_db_version_ = get_tgis_db_version() + if tgis_db_version_ > 2: self.D["number_of_semantic_labels"] = None self.set_raster_register(raster_register) diff --git a/python/grass/temporal/tests/grass_temporal_core_init_skip_db_test.py b/python/grass/temporal/tests/grass_temporal_core_init_skip_db_test.py index 252c5f89359..56a76e26c82 100644 --- a/python/grass/temporal/tests/grass_temporal_core_init_skip_db_test.py +++ b/python/grass/temporal/tests/grass_temporal_core_init_skip_db_test.py @@ -16,6 +16,19 @@ import grass.script as gs import grass.temporal as tgis +ds_types_dict = { + "strds": ("strds"), + "str3ds": ("str3ds"), + "stvds": ("stvds"), + "rast": ("raster", "rast"), + "raster": ("raster", "rast"), + "raster_3d": ("raster_3d", "rast3d", "raster3d"), + "rast3d": ("raster_3d", "rast3d", "raster3d"), + "raster3d": ("raster_3d", "rast3d", "raster3d"), + "vect": ("vector", "vect"), + "vector": ("vector", "vect"), +} + @pytest.fixture def simple_mapset(tmp_path): @@ -51,7 +64,7 @@ def test_init_creates_tgis_db_if_not_skipped(simple_mapset): assert ( sqlite3.connect(tgis_db) .execute( - "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%strds%';" + "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%strds%';", ) .fetchall() != [] @@ -74,3 +87,65 @@ def test_init_succeeds_without_db_creation(simple_mapset): assert tgis.get_sql_template_path().endswith("sql") assert not var_file.exists() assert not tgis_db.exists() + + +@pytest.mark.parametrize("ident", [None, "test@c"]) +def test_stds_construction_without_tgis_db_initialization( + simple_mapset, + ident: str | None, +): + """STDS objects can be constructed even if the temporal DB is not initialized. + + Empty STDS objects are constructed in several temporal modules, also where a + temporal database is not necessarily initialized. This test verifies that + STDS objects can be constructed even when tgis is initialized with + tgis.init(skip_db_init=True). + Methods that require access to the temporal database (e.g., is_in_db()) + are not tested (they would raise exceptions). + """ + with gs.setup.init(simple_mapset): + tgis.init(skip_db_init=True) + # None of these must raise if the temporal database is not initialized + strds = tgis.SpaceTimeRasterDataset(ident) + assert "number_of_semantic_labels" in strds.metadata.D + tgis.SpaceTimeRaster3DDataset(ident) + tgis.SpaceTimeVectorDataset(ident) + # Test dataset_factory for all dataset types, which should be constructed without DB access + for ds_type, valid_ds_types in ds_types_dict.items(): + stds = tgis.dataset_factory(ds_type, ident) + assert stds.get_id() == ident + assert stds.get_type() in valid_ds_types + + +@pytest.mark.parametrize("ident", [None, "test"]) +def test_stds_construction_with_tgis_db_initialization( + simple_mapset, + ident: str | None, +): + """STDS objects are constructed with access to the temporal DB if initialized. + + This is the counterpart to test_stds_construction_without_tgis_db_initialization: + When tgis.init() is called without skip_db_init=True, the temporal database is + accessible and STDS objects can access TGIS metadata from the DB (not the dataset). + Methods that require access to the temporal database (e.g., is_in_db()) + are tested to return successfully and valid. + + """ + with gs.setup.init(simple_mapset): + tgis.init() + dbif, _connection_state_changes = tgis.init_dbif(None) + if ident is not None: + ident = f"{ident}@{tgis.get_current_mapset()}" + assert tgis.get_tgis_metadata() is not None + assert tgis.get_tgis_db_version_from_metadata() == tgis.get_tgis_db_version() + tgis.get_current_mapset() + strds = tgis.SpaceTimeRasterDataset(ident) + assert strds.get_id() == ident + assert strds.is_in_db() is False + assert "number_of_semantic_labels" in strds.metadata.D + # Test dataset_factory for all dataset types, which should be constructed with DB access + for ds_type, valid_ds_types in ds_types_dict.items(): + stds = tgis.dataset_factory(ds_type, ident) + assert stds.get_id() == ident + assert stds.get_type() in valid_ds_types + assert stds.is_in_db(dbif) is False