import sys import uuid from datetime import datetime from pathlib import Path import freezegun import pytest from _pytest.monkeypatch import MonkeyPatch from _pytest.tmpdir import TempPathFactory from tarsafe import TarSafe import rasa.shared.utils.io from rasa.engine.graph import GraphModelConfiguration, GraphSchema, SchemaNode from rasa.engine.storage.local_model_storage import ( MODEL_ARCHIVE_METADATA_FILE, LocalModelStorage, ) from rasa.engine.storage.resource import Resource from rasa.engine.storage.storage import ModelMetadata, ModelStorage from rasa.exceptions import UnsupportedModelVersionError from rasa.shared.core.domain import Domain from rasa.shared.data import TrainingType from tests.engine.graph_components_test_classes import PersistableTestComponent def test_write_to_and_read(default_model_storage: ModelStorage): test_filename = "file.txt" test_file_content = "hi" test_sub_filename = "sub_file" test_sub_dir_name = "sub_directory" test_sub_file_content = "sub file" resource = Resource("some_node123") # Fill model storage for resource with default_model_storage.write_to(resource) as resource_directory: file = resource_directory / test_filename file.write_text(test_file_content) sub_directory = resource_directory / test_sub_dir_name sub_directory.mkdir() file_in_sub_directory = sub_directory / test_sub_filename file_in_sub_directory.write_text(test_sub_file_content) # Read written resource data from model storage to see whether all expected # content is there with default_model_storage.read_from(resource) as resource_directory: assert (resource_directory / test_filename).read_text() == test_file_content assert ( resource_directory / test_sub_dir_name / test_sub_filename ).read_text() == test_sub_file_content def test_read_from_not_existing_resource(default_model_storage: ModelStorage): with default_model_storage.write_to(Resource("resource1")) as temporary_directory: file = temporary_directory / "file.txt" file.write_text("test") with pytest.raises(ValueError): with default_model_storage.read_from(Resource("a different resource")) as _: pass def test_read_from_rasa2_resource(tmp_path_factory: TempPathFactory): # we only search for the fingerprint file - nlu and core folder do not even need # to exist model_dir = tmp_path_factory.mktemp("model_dir") version = "2.8.5" rasa.shared.utils.io.dump_obj_as_json_to_file( model_dir / "fingerprint.json", {"version": version, "irrelevant-other-key": "bla"}, ) model_zips = tmp_path_factory.mktemp("model_zips") resource_name = "model" with TarSafe.open(model_zips / resource_name, "w:gz") as tar: tar.add(model_dir, arcname="") storage_dir = tmp_path_factory.mktemp("storage_dir") storage = LocalModelStorage(storage_path=storage_dir) with pytest.raises(UnsupportedModelVersionError, match=f".*{version}.*"): storage.from_model_archive( storage_path=storage_dir, model_archive_path=model_zips / resource_name ) with pytest.raises(UnsupportedModelVersionError, match=f".*{version}.*"): storage.metadata_from_archive(model_archive_path=model_zips / resource_name) @pytest.mark.skipif( sys.platform != "win32", reason="no need to run this test on other platform than Windows", ) def test_read_long_resource_names_windows( tmp_path_factory: TempPathFactory, domain: Domain, ): model_dir = tmp_path_factory.mktemp("model_dir") version = "3.6.21" # full path length > 260 chars # but each component of the path needs to be below 255 chars # https://bugs.python.org/issue542314#msg10256 long_file_path = model_dir / f"{'custom_file' * 22}.json" rasa.shared.utils.io.dump_obj_as_json_to_file( Path(f"\\\\?\\{long_file_path}"), {"irrelevant-key": "bla"}, ) train_schema = GraphSchema( { "train": SchemaNode( needs={}, uses=PersistableTestComponent, fn="train", constructor_name="create", config={"some_config": 123455, "some more config": [{"nested": "hi"}]}, ), "load": SchemaNode( needs={"resource": "train"}, uses=PersistableTestComponent, fn="run_inference", constructor_name="load", config={}, is_target=True, ), } ) predict_schema = GraphSchema( { "run": SchemaNode( needs={}, uses=PersistableTestComponent, fn="run", constructor_name="load", config={"some_config": 123455, "some more config": [{"nested": "hi"}]}, ) } ) rasa.shared.utils.io.dump_obj_as_json_to_file( model_dir / MODEL_ARCHIVE_METADATA_FILE, ModelMetadata( trained_at=datetime.utcnow(), rasa_open_source_version=version, model_id="xxxxxxx", assistant_id="test_assistant", domain=domain, train_schema=train_schema, predict_schema=predict_schema, project_fingerprint="xxxxxxx", core_target="", nlu_target="", language="en", training_type=TrainingType.BOTH, ).as_dict(), ) model_zips = tmp_path_factory.mktemp("model_zips") resource_name = "model" with TarSafe.open(model_zips / resource_name, "w:gz") as tar: tar.add(Path(f"\\\\?\\{model_dir}"), arcname="") storage_dir = tmp_path_factory.mktemp("storage_dir") storage = LocalModelStorage(storage_path=storage_dir) storage.metadata_from_archive(model_archive_path=model_zips / resource_name) def test_create_model_package(tmp_path_factory: TempPathFactory, domain: Domain): train_model_storage = LocalModelStorage( tmp_path_factory.mktemp("train model storage") ) train_schema = GraphSchema( { "train": SchemaNode( needs={}, uses=PersistableTestComponent, fn="train", constructor_name="create", config={"some_config": 123455, "some more config": [{"nested": "hi"}]}, ), "load": SchemaNode( needs={"resource": "train"}, uses=PersistableTestComponent, fn="run_inference", constructor_name="load", config={}, is_target=True, ), } ) predict_schema = GraphSchema( { "run": SchemaNode( needs={}, uses=PersistableTestComponent, fn="run", constructor_name="load", config={"some_config": 123455, "some more config": [{"nested": "hi"}]}, ) } ) # Fill model Storage with train_model_storage.write_to(Resource("resource1")) as directory: file = directory / "file.txt" file.write_text("test") # Package model persisted_model_dir = tmp_path_factory.mktemp("persisted models") archive_path = persisted_model_dir / "my-model.tar.gz" trained_at = datetime.utcnow() with freezegun.freeze_time(trained_at): train_model_storage.create_model_package( archive_path, GraphModelConfiguration( train_schema, predict_schema, TrainingType.BOTH, "test_assistant", None, None, "nlu", ), domain, ) # Unpack and inspect packaged model load_model_storage_dir = tmp_path_factory.mktemp("load model storage") just_packaged_metadata = LocalModelStorage.metadata_from_archive(archive_path) (load_model_storage, packaged_metadata) = LocalModelStorage.from_model_archive( load_model_storage_dir, archive_path ) assert just_packaged_metadata.trained_at == packaged_metadata.trained_at assert packaged_metadata.train_schema == train_schema assert packaged_metadata.predict_schema == predict_schema assert packaged_metadata.domain.as_dict() == domain.as_dict() assert packaged_metadata.rasa_open_source_version == rasa.__version__ assert packaged_metadata.trained_at == trained_at assert packaged_metadata.model_id assert packaged_metadata.assistant_id == "test_assistant" assert packaged_metadata.project_fingerprint persisted_resources = load_model_storage_dir.glob("*") assert list(persisted_resources) == [Path(load_model_storage_dir, "resource1")] def test_read_unsupported_model( monkeypatch: MonkeyPatch, tmp_path_factory: TempPathFactory, domain: Domain ): train_model_storage = LocalModelStorage( tmp_path_factory.mktemp("train model storage") ) graph_schema = GraphSchema(nodes={}) persisted_model_dir = tmp_path_factory.mktemp("persisted models") archive_path = persisted_model_dir / "my-model.tar.gz" # Create outdated model meta data trained_at = datetime.utcnow() model_configuration = GraphModelConfiguration( graph_schema, graph_schema, TrainingType.BOTH, "test_assistant", None, None, "nlu", ) outdated_model_meta_data = ModelMetadata( trained_at=trained_at, rasa_open_source_version=rasa.__version__, # overwrite later to avoid error model_id=uuid.uuid4().hex, assistant_id=model_configuration.assistant_id, domain=domain, train_schema=model_configuration.train_schema, predict_schema=model_configuration.predict_schema, training_type=model_configuration.training_type, project_fingerprint=rasa.model.project_fingerprint(), language=model_configuration.language, core_target=model_configuration.core_target, nlu_target=model_configuration.nlu_target, ) old_version = "0.0.1" outdated_model_meta_data.rasa_open_source_version = old_version # Package model - and inject the outdated model meta data monkeypatch.setattr( LocalModelStorage, "_create_model_metadata", lambda *args, **kwargs: outdated_model_meta_data, ) train_model_storage.create_model_package( model_archive_path=archive_path, model_configuration=model_configuration, domain=domain, ) # Unpack and inspect packaged model load_model_storage_dir = tmp_path_factory.mktemp("load model storage") expected_message = ( f"The model version is trained using Rasa Open Source " f"{old_version} and is not compatible with your current " f"installation .*" ) with pytest.raises(UnsupportedModelVersionError, match=expected_message): LocalModelStorage.metadata_from_archive(archive_path) with pytest.raises(UnsupportedModelVersionError, match=expected_message): LocalModelStorage.from_model_archive(load_model_storage_dir, archive_path) def test_create_package_with_non_existing_parent(tmp_path: Path): storage = LocalModelStorage.create(tmp_path) model_file = tmp_path / "new" / "sub" / "dir" / "file.tar.gz" storage.create_model_package( model_file, GraphModelConfiguration( GraphSchema({}), GraphSchema({}), TrainingType.BOTH, "test_assistant", None, None, "nlu", ), Domain.empty(), ) assert model_file.is_file() def test_create_model_package_with_non_empty_model_storage(tmp_path: Path): # Put something in the future model storage directory (tmp_path / "somefile.json").touch() with pytest.raises(ValueError): # Unpacking into an already filled `ModelStorage` raises an exception. _ = LocalModelStorage.from_model_archive(tmp_path, Path("does not matter")) def test_create_model_package_with_non_existing_dir( tmp_path: Path, default_model_storage: ModelStorage ): path = tmp_path / "some_dir" / "another" / "model.tar.gz" default_model_storage.create_model_package( path, GraphModelConfiguration( GraphSchema({}), GraphSchema({}), TrainingType.BOTH, "test_assistant", None, None, "nlu", ), Domain.empty(), ) assert path.exists()