diff --git a/protostar/configuration_file/configuration_file.py b/protostar/configuration_file/configuration_file.py index c250977f15..f5e170642a 100644 --- a/protostar/configuration_file/configuration_file.py +++ b/protostar/configuration_file/configuration_file.py @@ -3,7 +3,7 @@ from typing import Any, Generic, Optional, TypeVar, Union from protostar.protostar_exception import ProtostarException -from protostar.utils.protostar_directory import VersionType +from protostar.self import DeclaredProtostarVersionProviderProtocol, ProtostarVersion PrimitiveTypesSupportedByConfigurationFile = Union[str, int, bool] @@ -43,9 +43,11 @@ def create_file_content( ... -class ConfigurationFile(Generic[ConfigurationFileModelT]): +class ConfigurationFile( + DeclaredProtostarVersionProviderProtocol, Generic[ConfigurationFileModelT] +): @abstractmethod - def get_min_protostar_version(self) -> Optional[VersionType]: + def get_declared_protostar_version(self) -> Optional[ProtostarVersion]: ... @abstractmethod diff --git a/protostar/configuration_file/configuration_file_v1.py b/protostar/configuration_file/configuration_file_v1.py index 51548157de..f665508c1e 100644 --- a/protostar/configuration_file/configuration_file_v1.py +++ b/protostar/configuration_file/configuration_file_v1.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Optional, Union -from protostar.utils.protostar_directory import VersionManager, VersionType +from protostar.self import ProtostarVersion, parse_protostar_version from .configuration_file import ( CommandConfig, @@ -47,7 +47,7 @@ def __init__( "migrate", ] - def get_min_protostar_version(self) -> Optional[VersionType]: + def get_declared_protostar_version(self) -> Optional[ProtostarVersion]: version_str = self._configuration_file_interpreter.get_attribute( attribute_name="protostar_version", section_name="config", @@ -55,7 +55,7 @@ def get_min_protostar_version(self) -> Optional[VersionType]: ) if not version_str: return None - return VersionManager.parse(version_str) + return parse_protostar_version(version_str) def get_contract_names(self) -> list[str]: contract_section = self._configuration_file_interpreter.get_section( @@ -109,7 +109,7 @@ def read( self, ) -> ConfigurationFileV1Model: return ConfigurationFileV1Model( - protostar_version=self._get_min_protostar_version_str(), + protostar_version=self._get_declared_protostar_version_str(), libs_path_str=self._get_libs_path_str(), contract_name_to_path_strs=self._get_contract_name_to_path_strs(), command_name_to_config=self._get_command_name_to_config(), @@ -118,8 +118,8 @@ def read( profile_name_to_shared_command_config=self._get_profile_name_to_shared_command_config(), ) - def _get_min_protostar_version_str(self) -> Optional[str]: - version = self.get_min_protostar_version() + def _get_declared_protostar_version_str(self) -> Optional[str]: + version = self.get_declared_protostar_version() if not version: return None return str(version) diff --git a/protostar/configuration_file/configuration_file_v1_test.py b/protostar/configuration_file/configuration_file_v1_test.py index 94a3f3df4d..4bb393c73a 100644 --- a/protostar/configuration_file/configuration_file_v1_test.py +++ b/protostar/configuration_file/configuration_file_v1_test.py @@ -5,7 +5,7 @@ from protostar.configuration_file.configuration_legacy_toml_interpreter import ( ConfigurationLegacyTOMLInterpreter, ) -from protostar.utils import VersionManager +from protostar.self import parse_protostar_version from .configuration_file_v1 import ( ConfigurationFile, @@ -56,10 +56,10 @@ def configuration_file_fixture( """, ], ) -def test_retrieving_min_protostar_version(configuration_file: ConfigurationFile): - result = configuration_file.get_min_protostar_version() +def test_retrieving_declared_protostar_version(configuration_file: ConfigurationFile): + result = configuration_file.get_declared_protostar_version() - assert result == VersionManager.parse("0.1.2") + assert result == parse_protostar_version("0.1.2") @pytest.mark.parametrize( diff --git a/protostar/configuration_file/configuration_file_v2.py b/protostar/configuration_file/configuration_file_v2.py index 1c76beb757..cd1d29ef48 100644 --- a/protostar/configuration_file/configuration_file_v2.py +++ b/protostar/configuration_file/configuration_file_v2.py @@ -4,7 +4,7 @@ from typing_extensions import Self -from protostar.utils.protostar_directory import VersionManager, VersionType +from protostar.self import ProtostarVersion, parse_protostar_version from .configuration_file import ( CommandConfig, @@ -23,7 +23,7 @@ @dataclass class ConfigurationFileV2Model: - min_protostar_version: Optional[str] + protostar_version: Optional[str] contract_name_to_path_strs: dict[ContractName, list[str]] project_config: CommandConfig command_name_to_config: CommandNameToConfig @@ -32,7 +32,7 @@ class ConfigurationFileV2Model: @classmethod # pylint: disable=invalid-name - def from_v1(cls, v1: ConfigurationFileV1Model, min_protostar_version: str) -> Self: + def from_v1(cls, v1: ConfigurationFileV1Model, protostar_version: str) -> Self: project_config = v1.shared_command_config if v1.libs_path_str: project_config = { @@ -40,7 +40,7 @@ def from_v1(cls, v1: ConfigurationFileV1Model, min_protostar_version: str) -> Se **v1.shared_command_config, } return cls( - min_protostar_version=min_protostar_version, + protostar_version=protostar_version, contract_name_to_path_strs=v1.contract_name_to_path_strs, command_name_to_config=v1.command_name_to_config, profile_name_to_commands_config=v1.profile_name_to_commands_config, @@ -64,13 +64,13 @@ def __init__( self._configuration_file_reader = configuration_file_reader self._filename = filename - def get_min_protostar_version(self) -> Optional[VersionType]: + def get_declared_protostar_version(self) -> Optional[ProtostarVersion]: version_str = self._configuration_file_reader.get_attribute( attribute_name="min-protostar-version", section_name="project" ) if not version_str: return None - return VersionManager.parse(version_str) + return parse_protostar_version(version_str) def get_contract_names(self) -> list[str]: contract_section = self._configuration_file_reader.get_section("contracts") @@ -145,9 +145,7 @@ def create_file_content( @staticmethod def _prepare_project_section_data(model: ConfigurationFileV2Model) -> dict: project_config_section = {} - project_config_section["min-protostar-version"] = str( - model.min_protostar_version - ) + project_config_section["min-protostar-version"] = str(model.protostar_version) project_config_section: dict = { **project_config_section, **model.project_config, diff --git a/protostar/configuration_file/configuration_file_v2_test.py b/protostar/configuration_file/configuration_file_v2_test.py index 210f5907e2..7dfd0e74ce 100644 --- a/protostar/configuration_file/configuration_file_v2_test.py +++ b/protostar/configuration_file/configuration_file_v2_test.py @@ -9,7 +9,7 @@ from protostar.configuration_file.configuration_toml_interpreter import ( ConfigurationTOMLInterpreter, ) -from protostar.utils.protostar_directory import VersionManager +from protostar.self import parse_protostar_version from .configuration_file import ( ConfigurationFile, @@ -67,10 +67,10 @@ def configuration_file_fixture(project_root_path: Path, protostar_toml_content: ) -def test_retrieving_min_protostar_version(configuration_file: ConfigurationFile): - min_protostar_version = configuration_file.get_min_protostar_version() +def test_retrieving_declared_protostar_version(configuration_file: ConfigurationFile): + declared_protostar_version = configuration_file.get_declared_protostar_version() - assert min_protostar_version == VersionManager.parse("9.9.9") + assert declared_protostar_version == parse_protostar_version("9.9.9") def test_retrieving_contract_names(configuration_file: ConfigurationFile): @@ -122,7 +122,7 @@ def test_saving_configuration( ): content_configurator = configuration_file configuration_file_v2_model = ConfigurationFileV2Model( - min_protostar_version="9.9.9", + protostar_version="9.9.9", project_config={ "lib-path": "lib", "no-color": True, @@ -163,10 +163,10 @@ def test_transforming_model_v1_into_v2(): profile_name_to_shared_command_config={"devnet": {"arg_name": 24}}, ) - model_v2 = ConfigurationFileV2Model.from_v1(model_v1, min_protostar_version="0.4.0") + model_v2 = ConfigurationFileV2Model.from_v1(model_v1, protostar_version="0.4.0") assert model_v2 == ConfigurationFileV2Model( - min_protostar_version="0.4.0", + protostar_version="0.4.0", command_name_to_config={"deploy": {"arg_name": 21}}, contract_name_to_path_strs={"main": ["src/main.cairo"]}, project_config={"arg_name": 42, "lib-path": "lib"}, @@ -226,7 +226,7 @@ def test_transforming_file_v1_into_v2(protostar_toml_content: str): filename="_", ).create_file_content( content_builder=ConfigurationTOMLContentBuilder(), - model=ConfigurationFileV2Model.from_v1(model_v1, min_protostar_version="9.9.9"), + model=ConfigurationFileV2Model.from_v1(model_v1, protostar_version="9.9.9"), ) assert transformed_protostar_toml == protostar_toml_content @@ -237,7 +237,7 @@ def test_saving_in_particular_order( ): content_configurator = configuration_file configuration_file_v2_model = ConfigurationFileV2Model( - min_protostar_version="9.9.9", + protostar_version="9.9.9", project_config={ "lib-path": "./lib", "cairo-path": ["bar"], diff --git a/protostar/self/__init__.py b/protostar/self/__init__.py new file mode 100644 index 0000000000..b9574bd654 --- /dev/null +++ b/protostar/self/__init__.py @@ -0,0 +1,5 @@ +from .protostar_compatibility_with_project_checker import ( + DeclaredProtostarVersionProviderProtocol, + ProtostarVersion, + parse_protostar_version, +) diff --git a/protostar/self/protostar_compatibility_with_project_checker.py b/protostar/self/protostar_compatibility_with_project_checker.py new file mode 100644 index 0000000000..aa934df0d6 --- /dev/null +++ b/protostar/self/protostar_compatibility_with_project_checker.py @@ -0,0 +1,56 @@ +from enum import Enum, auto +from typing import Optional, Protocol + +from packaging import version + +ProtostarVersion = version.Version + + +class DeclaredProtostarVersionProviderProtocol(Protocol): + def get_declared_protostar_version(self) -> Optional[ProtostarVersion]: + ... + + +class ProtostarVersionProviderProtocol(Protocol): + def get_protostar_version(self) -> ProtostarVersion: + ... + + +class CompatibilityCheckResult(Enum): + COMPATIBLE = auto() + OUTDATED_PROTOSTAR = auto() + OUTDATED_DECLARED_VERSION = auto() + FAILURE = auto() + + +class ProtostarCompatibilityWithProjectChecker: + def __init__( + self, + protostar_version_provider: ProtostarVersionProviderProtocol, + declared_protostar_version_provider: DeclaredProtostarVersionProviderProtocol, + ) -> None: + self._protostar_version_provider = protostar_version_provider + self._declared_protostar_version_provider = declared_protostar_version_provider + + def check_compatibility(self) -> CompatibilityCheckResult: + protostar_version = self._protostar_version_provider.get_protostar_version() + declared_protostar_version = ( + self._declared_protostar_version_provider.get_declared_protostar_version() + ) + if declared_protostar_version is None: + return CompatibilityCheckResult.FAILURE + if ( + declared_protostar_version.major == protostar_version.major + and declared_protostar_version.minor == protostar_version.minor + and declared_protostar_version.micro <= protostar_version.micro + ): + return CompatibilityCheckResult.COMPATIBLE + if declared_protostar_version < protostar_version: + return CompatibilityCheckResult.OUTDATED_DECLARED_VERSION + return CompatibilityCheckResult.OUTDATED_PROTOSTAR + + +def parse_protostar_version(value: str) -> ProtostarVersion: + result = version.parse(value) + assert isinstance(result, ProtostarVersion) + return result diff --git a/protostar/self/protostar_compatibility_with_project_checker_test.py b/protostar/self/protostar_compatibility_with_project_checker_test.py new file mode 100644 index 0000000000..cbf924bd45 --- /dev/null +++ b/protostar/self/protostar_compatibility_with_project_checker_test.py @@ -0,0 +1,62 @@ +import pytest + +from .protostar_compatibility_with_project_checker import ( + CompatibilityCheckResult, + DeclaredProtostarVersionProviderProtocol, + ProtostarCompatibilityWithProjectChecker, + ProtostarVersionProviderProtocol, + parse_protostar_version, +) + + +class DeclaredProtostarVersionProviderDouble(DeclaredProtostarVersionProviderProtocol): + def __init__(self, declared_protostar_version_str: str): + self._declared_protostar_version_str = declared_protostar_version_str + + def get_declared_protostar_version(self): + return parse_protostar_version(self._declared_protostar_version_str) + + +class ProtostarVersionProviderDouble(ProtostarVersionProviderProtocol): + def __init__(self, protostar_version_str: str): + self._protostar_version_str = protostar_version_str + + def get_protostar_version(self): + return parse_protostar_version(self._protostar_version_str) + + +@pytest.fixture(name="declared_protostar_version_provider") +def declared_protostar_version_provider_fixture(declared_protostar_version: str): + return DeclaredProtostarVersionProviderDouble(declared_protostar_version) + + +@pytest.fixture(name="protostar_version_provider") +def protostar_version_provider_fixture(protostar_version: str): + return ProtostarVersionProviderDouble(protostar_version) + + +@pytest.mark.parametrize( + "protostar_version, declared_protostar_version, is_compatible", + ( + ("0.1.2", "0.1.2", CompatibilityCheckResult.COMPATIBLE), + ("0.1.2", "0.1.1", CompatibilityCheckResult.COMPATIBLE), + ("1.0.0", "1.0.0", CompatibilityCheckResult.COMPATIBLE), + ("0.1.1", "0.1.2", CompatibilityCheckResult.OUTDATED_PROTOSTAR), + ("1.0.0", "1.1.0", CompatibilityCheckResult.OUTDATED_PROTOSTAR), + ("0.2.0", "0.1.2", CompatibilityCheckResult.OUTDATED_DECLARED_VERSION), + ("1.0.0", "0.9.0", CompatibilityCheckResult.OUTDATED_DECLARED_VERSION), + ), +) +def test_compatibility( + declared_protostar_version_provider: DeclaredProtostarVersionProviderProtocol, + protostar_version_provider: ProtostarVersionProviderProtocol, + is_compatible: bool, +): + compatibility_checker = ProtostarCompatibilityWithProjectChecker( + protostar_version_provider, + declared_protostar_version_provider, + ) + + result = compatibility_checker.check_compatibility() + + assert result == is_compatible