Skip to content

Commit

Permalink
fix: specify encoding always when writing text to a file (ApeWorX#2129)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Jun 12, 2024
1 parent e0e57d4 commit 882526e
Show file tree
Hide file tree
Showing 18 changed files with 78 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/ape/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,10 @@ def write_to_disk(self, destination: Path, replace: bool = False):
yaml.safe_dump(data, file)

elif destination.suffix == ".json":
destination.write_text(self.model_dump_json(by_alias=True))
destination.write_text(self.model_dump_json(by_alias=True), encoding="utf8")

else:
raise ValueError(f"Unsupported destination file type {destination}.")
raise ConfigError(f"Unsupported destination file type '{destination}'.")


def _get_compile_configs_from_manifest(manifest: PackageManifest) -> dict[str, dict]:
Expand Down
8 changes: 4 additions & 4 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1345,22 +1345,22 @@ def _get_contract_creation_from_disk(self, address: AddressType) -> Optional[Con
def _cache_contract_to_disk(self, address: AddressType, contract_type: ContractType):
self._contract_types_cache.mkdir(exist_ok=True, parents=True)
address_file = self._contract_types_cache / f"{address}.json"
address_file.write_text(contract_type.model_dump_json())
address_file.write_text(contract_type.model_dump_json(), encoding="utf8")

def _cache_proxy_info_to_disk(self, address: AddressType, proxy_info: ProxyInfoAPI):
self._proxy_info_cache.mkdir(exist_ok=True, parents=True)
address_file = self._proxy_info_cache / f"{address}.json"
address_file.write_text(proxy_info.model_dump_json())
address_file.write_text(proxy_info.model_dump_json(), encoding="utf8")

def _cache_blueprint_to_disk(self, blueprint_id: str, contract_type: ContractType):
self._blueprint_cache.mkdir(exist_ok=True, parents=True)
blueprint_file = self._blueprint_cache / f"{blueprint_id}.json"
blueprint_file.write_text(contract_type.model_dump_json())
blueprint_file.write_text(contract_type.model_dump_json(), encoding="utf8")

def _cache_contract_creation_to_disk(self, address: AddressType, creation: ContractCreation):
self._contract_creation_cache.mkdir(exist_ok=True, parents=True)
path = self._contract_creation_cache / f"{address}.json"
path.write_text(creation.model_dump_json())
path.write_text(creation.model_dump_json(), encoding="utf8")

def _load_deployments_cache(self) -> dict:
return (
Expand Down
10 changes: 5 additions & 5 deletions src/ape/managers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ def cache_api(self, api: DependencyAPI) -> Path:
exclude_defaults=True,
)

api_file.write_text(json_text)
api_file.write_text(json_text, encoding="utf8")
return api_file

def remove(self, package_id: str, version: str):
Expand Down Expand Up @@ -1605,7 +1605,7 @@ def unpack(self, destination: Path, config_override: Optional[dict] = None) -> "
for source_id, src in sources.items():
path = destination / source_id
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(str(src.content))
path.write_text(str(src.content), encoding="utf8")

# Unpack config file.
self.config.write_to_disk(destination / "ape-config.yaml")
Expand Down Expand Up @@ -1899,7 +1899,7 @@ def track(self, contract: ContractInstance):
# NOTE: missing_ok=True to handle race condition.
destination.unlink(missing_ok=True)

destination.write_text(artifact.model_dump_json())
destination.write_text(artifact.model_dump_json(), encoding="utf8")

def __iter__(self) -> Iterator[EthPMContractInstance]:
"""
Expand Down Expand Up @@ -2318,7 +2318,7 @@ def update_manifest(self, **kwargs):
self.manifest_path.unlink(missing_ok=True)
manifest_text = self.manifest.model_dump_json(mode="json", by_alias=True)
self.manifest_path.parent.mkdir(parents=True, exist_ok=True)
self.manifest_path.write_text(manifest_text)
self.manifest_path.write_text(manifest_text, encoding="utf8")

def load_contracts(
self, *source_ids: Union[str, Path], use_cache: bool = True
Expand Down Expand Up @@ -2414,7 +2414,7 @@ def _update_contract_types(self, contract_types: dict[str, ContractType]):
abi_json = json.dumps(
[x.model_dump_json(by_alias=True, mode="json") for x in ct.abi]
)
file.write_text(abi_json)
file.write_text(abi_json, encoding="utf8")


def _find_directory_with_extension(
Expand Down
6 changes: 3 additions & 3 deletions src/ape/types/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ def write_xml(self, path: Path):
path = path / "coverage.xml"

path.unlink(missing_ok=True)
path.write_text(xml)
path.write_text(xml, encoding="utf8")

def write_html(self, path: Path, verbose: bool = False):
if not (html := self.get_html(verbose=verbose)):
Expand All @@ -833,7 +833,7 @@ def write_html(self, path: Path, verbose: bool = False):
# Create new index.html.
index = html_path / "index.html"
index.unlink(missing_ok=True)
index.write_text(html)
index.write_text(html, encoding="utf8")

favicon = html_path / "favicon.ico"
if not favicon.is_file():
Expand Down Expand Up @@ -862,7 +862,7 @@ def write_html(self, path: Path, verbose: bool = False):

css = html_path / "styles.css"
css.unlink(missing_ok=True)
css.write_text(_CSS)
css.write_text(_CSS, encoding="utf8")

def get_html(self, verbose: bool = False) -> str:
"""
Expand Down
8 changes: 5 additions & 3 deletions src/ape_accounts/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def public_key(self) -> HexBytes:
key_file_data = self.keyfile
key_file_data["public_key"] = publicKey[2:]

self.keyfile_path.write_text(json.dumps(key_file_data))
self.keyfile_path.write_text(json.dumps(key_file_data), encoding="utf8")

return HexBytes(bytes.fromhex(publicKey[2:]))

Expand Down Expand Up @@ -148,7 +148,9 @@ def change_password(self):
key = self.__key

passphrase = self._prompt_for_passphrase("Create new passphrase", confirmation_prompt=True)
self.keyfile_path.write_text(json.dumps(EthAccount.encrypt(key, passphrase)))
self.keyfile_path.write_text(
json.dumps(EthAccount.encrypt(key, passphrase)), encoding="utf8"
)

def delete(self):
passphrase = self._prompt_for_passphrase(
Expand Down Expand Up @@ -293,7 +295,7 @@ def _write_and_return_account(alias: str, passphrase: str, account: LocalAccount
path = ManagerAccessMixin.account_manager.containers["accounts"].data_folder.joinpath(
f"{alias}.json"
)
path.write_text(json.dumps(EthAccount.encrypt(account.key, passphrase)))
path.write_text(json.dumps(EthAccount.encrypt(account.key, passphrase)), encoding="utf8")

return KeyfileAccount(keyfile_path=path)

Expand Down
4 changes: 2 additions & 2 deletions src/ape_init/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ def cli(cli_ctx, github):
cli_ctx.logger.warning(f"Unable to create .gitignore: '{git_ignore_path}' file exists.")
else:
git_ignore_path.touch()
git_ignore_path.write_text(GITIGNORE_CONTENT.lstrip())
git_ignore_path.write_text(GITIGNORE_CONTENT.lstrip(), encoding="utf8")

ape_config = project_folder / CONFIG_FILE_NAME
if ape_config.exists():
cli_ctx.logger.warning(f"'{ape_config}' exists")
else:
project_name = click.prompt("Please enter project name")
ape_config.write_text(f"name: {project_name}\n")
ape_config.write_text(f"name: {project_name}\n", encoding="utf8")
cli_ctx.logger.success(f"{project_name} is written in {CONFIG_FILE_NAME}")
2 changes: 1 addition & 1 deletion src/ape_pm/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def fetch(self, destination: Path):

destination.unlink(missing_ok=True)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(self.local.read_text())
destination.write_text(self.local.read_text(), encoding="utf8")


class GithubDependency(DependencyAPI):
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def _make_keyfile_account(base_path: Path, alias: str, params: dict, funder):
# Corrupted from a previous test
test_keyfile_path.unlink()

test_keyfile_path.write_text(json.dumps(params))
test_keyfile_path.write_text(json.dumps(params), encoding="utf8")
acct = ape.accounts.load(alias)
funder.transfer(acct, "25 ETH") # Auto-fund this account
return acct
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ def test_contract_file_paths_argument_handles_exclude(
# make a .cache file to show it is ignored.
cache_file = project_with_contract.contracts_folder / ".cache" / "thing.json"
cache_file.parent.mkdir(parents=True, exist_ok=True)
cache_file.write_text("FAILS IF LOADED")
cache_file.write_text("FAILS IF LOADED", encoding="utf8")

result = runner.invoke(contracts_paths_cmd, "contracts")
assert "Exclude.json" not in result.output
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def test_contract_type_collision(compilers, project_with_contract, mock_compiler
new_contract = project_with_contract.path / existing_contract.source_id.replace(
".json", mock_compiler.ext
)
new_contract.write_text("foobar")
new_contract.write_text("foobar", encoding="utf8")
to_compile = [existing_path, new_contract]
compile = compilers.compile(to_compile, project=project_with_contract)

Expand All @@ -100,7 +100,7 @@ def test_contract_type_collision(compilers, project_with_contract, mock_compiler

def test_compile_with_settings(mock_compiler, compilers, project_with_contract):
new_contract = project_with_contract.path / f"AMockContract{mock_compiler.ext}"
new_contract.write_text("foobar")
new_contract.write_text("foobar", encoding="utf8")
settings = {"mock": {"foo": "bar"}}

_ = compilers.registered_compilers # Ensures cached property is set.
Expand Down
24 changes: 23 additions & 1 deletion tests/functional/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ape.exceptions import ConfigError
from ape.managers.config import CONFIG_FILE_NAME, merge_configs
from ape.types import GasLimit
from ape.utils import create_tempdir
from ape_ethereum.ecosystem import EthereumConfig, NetworkConfig
from ape_networks import CustomNetwork
from tests.functional.conftest import PROJECT_WITH_LONG_CONTRACTS_FOLDER
Expand Down Expand Up @@ -219,7 +220,7 @@ def test_global_config(data_folder, config):
test:
number_of_accounts: 11
""".strip()
config_file.write_text(config_content)
config_file.write_text(config_content, encoding="utf8")
global_config = config.load_global_config()
assert global_config.get_config("test").number_of_accounts == 11
config_file.unlink(missing_ok=True)
Expand Down Expand Up @@ -346,3 +347,24 @@ def hacked_in_method():

finally:
config.local_project.config._get_config_plugin_classes = original_method


def test_write_to_disk_json(config):
with create_tempdir() as base_path:
path = base_path / "config.json"
config.write_to_disk(path)
assert path.is_file()


def test_write_to_disk_yaml(config):
with create_tempdir() as base_path:
path = base_path / "config.yaml"
config.write_to_disk(path)
assert path.is_file()


def test_write_to_disk_txt(config):
with create_tempdir() as base_path:
path = base_path / "config.txt"
with pytest.raises(ConfigError, match=f"Unsupported destination file type '{path}'."):
config.write_to_disk(path)
2 changes: 1 addition & 1 deletion tests/functional/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def init_profile(source_cov, src):
# Create a source file.
file = tmp.path / "contracts" / filename
file.parent.mkdir(exist_ok=True, parents=True)
file.write_text("testing")
file.write_text("testing", encoding="utf8")

# Ensure the TB refers to this source.
tb_data["source_path"] = f"{tmp.path}/contracts/{filename}"
Expand Down
16 changes: 8 additions & 8 deletions tests/functional/test_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ def project_with_downloaded_dependencies(project, oz_dependencies_config):
manifest_source = oz_manifests / version / "openzeppelin.json"
manifest_dest = oz_manifests_dest / f"{version.replace('.', '_')}.json"
manifest_dest.unlink(missing_ok=True)
manifest_dest.write_text(manifest_source.read_text())
manifest_dest.write_text(manifest_source.read_text(), encoding="utf8")

# Also, copy in the API data
api_dest = base.api_folder / package_id / f"{version.replace('.', '_')}.json"
api_dest.unlink(missing_ok=True)
cfg = [x for x in oz_dependencies_config["dependencies"] if x["version"] == version][0]
api_dest.parent.mkdir(exist_ok=True, parents=True)
api_dest.write_text(json.dumps(cfg))
api_dest.write_text(json.dumps(cfg), encoding="utf8")

with project.temp_config(**oz_dependencies_config):
yield project
Expand Down Expand Up @@ -125,7 +125,7 @@ def test_decode_dependency_with_config_override(project):
base_path = tmp_project.path / path
contracts_path = base_path / "contracts"
contracts_path.mkdir(parents=True, exist_ok=True)
(contracts_path / "contract.json").write_text('{"abi": []}')
(contracts_path / "contract.json").write_text('{"abi": []}', encoding="utf8")

data = {"name": "FooBar", "local": path, "config_override": settings}
dependency = tmp_project.dependencies.decode_dependency(**data)
Expand Down Expand Up @@ -186,7 +186,7 @@ def test_add(project):
with project.isolate_in_tempdir() as tmp_project:
contracts_path = tmp_project.path / "src"
contracts_path.mkdir(exist_ok=True, parents=True)
(contracts_path / "contract.json").write_text('{"abi": []}')
(contracts_path / "contract.json").write_text('{"abi": []}', encoding="utf8")
data = {"name": "FooBar", "local": f"{tmp_project.path}"}

dependency = project.dependencies.add(data)
Expand Down Expand Up @@ -217,7 +217,7 @@ def test_install(project, mocker):
with project.isolate_in_tempdir() as tmp_project:
contracts_path = tmp_project.path / "src"
contracts_path.mkdir(exist_ok=True, parents=True)
(contracts_path / "contract.json").write_text('{"abi": []}')
(contracts_path / "contract.json").write_text('{"abi": []}', encoding="utf8")
data = {"name": "FooBar", "local": f"{tmp_project.path}"}
get_spec_spy = mocker.spy(tmp_project.dependencies, "_get_specified")
install_dep_spy = mocker.spy(tmp_project.dependencies, "install_dependency")
Expand Down Expand Up @@ -369,10 +369,10 @@ def node_modules_path(self, project_with_npm_dependency, request, mock_home_dire
contracts_folder = package_folder / "contracts"
contracts_folder.mkdir(parents=True)
package_json = package_folder / "package.json"
package_json.write_text(f'{{"version": "{self.VERSION}"}}')
package_json.write_text(f'{{"version": "{self.VERSION}"}}', encoding="utf8")
file = contracts_folder / "contract.json"
source_content = '{"abi": []}'
file.write_text(source_content)
file.write_text(source_content, encoding="utf8")
yield base

def test_fetch(self, node_modules_path, project_with_npm_dependency):
Expand Down Expand Up @@ -582,7 +582,7 @@ def test_load_contracts_with_config_override(self, project):
override = {"contracts_folder": "src"}
contracts_path = tmp_project.path / "src"
contracts_path.mkdir(exist_ok=True, parents=True)
(contracts_path / "contract.json").write_text('{"abi": []}')
(contracts_path / "contract.json").write_text('{"abi": []}', encoding="utf8")

# use_cache=False only to lessen stateful test clobbering.
data = {
Expand Down
Loading

0 comments on commit 882526e

Please sign in to comment.