diff --git a/src/ape/api/projects.py b/src/ape/api/projects.py index b386257682..a213382252 100644 --- a/src/ape/api/projects.py +++ b/src/ape/api/projects.py @@ -137,19 +137,22 @@ def cached_manifest(self) -> Optional[PackageManifest]: @property def contracts(self) -> Dict[str, ContractType]: - if self._contracts is None: - contracts = {} - for p in self._cache_folder.glob("*.json"): - if p == self.manifest_cachefile: - continue + if self._contracts is not None: + return self._contracts + + contracts = {} + for p in self._cache_folder.glob("*.json"): + if p == self.manifest_cachefile: + continue + + contract_name = p.stem + contract_type = ContractType.parse_file(p) + if contract_type.name is None: + contract_type.name = contract_name - contract_name = p.stem - contract_type = ContractType.parse_file(p) - if contract_type.name is None: - contract_type.name = contract_name + contracts[contract_type.name] = contract_type - contracts[contract_type.name] = contract_type - self._contracts = contracts + self._contracts = contracts return self._contracts @property diff --git a/src/ape/managers/project/manager.py b/src/ape/managers/project/manager.py index 69dc1ec921..c214a41afb 100644 --- a/src/ape/managers/project/manager.py +++ b/src/ape/managers/project/manager.py @@ -407,7 +407,7 @@ def contracts(self) -> Dict[str, ContractType]: Returns: Dict[str, ``ContractType``] """ - if self.local_project._cached_manifest is None: + if self.local_project.cached_manifest is None: return self.load_contracts() return self.local_project.contracts diff --git a/tests/functional/test_project.py b/tests/functional/test_project.py index 4829a64a87..1b81ca2a6d 100644 --- a/tests/functional/test_project.py +++ b/tests/functional/test_project.py @@ -1,3 +1,4 @@ +import os import shutil from pathlib import Path from urllib.parse import urlparse @@ -394,3 +395,21 @@ def test_getattr_contract_not_exists(project): contract.touch() with pytest.raises(AttributeError, match=expected): _ = project.ThisIsNotAContractThatExists + + +def test_build_file_only_modified_once(project_with_contract): + project = project_with_contract + artifact = project.path / ".build" / "__local__.json" + _ = project.contracts # Ensure compiled. + + # NOTE: This is how re-create the bug. Delete the underscore-prefixed + # cached object and attempt to re-compile. Previously, the ProjectManager + # was relying on an internal cache rather than the external one, and thus + # caused the file to get unnecessarily re-made (modified). + project.local_project._cached_manifest = None + + # Prove the file is not unnecessarily modified. + time_before = os.path.getmtime(artifact) + _ = project.contracts + time_after = os.path.getmtime(artifact) + assert time_before == time_after