From 9c9ed3cc7799922db11ed96296ba218ac0188cda Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:24 -0600 Subject: [PATCH 01/10] setup a failing test --- poetry.lock | 21 ++++++++++++++++++-- pyproject.toml | 2 +- tests/conftest.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index f7bd279f..626cfb73 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "better-diff" @@ -419,6 +419,23 @@ files = [ click = ">=6.0" pytest = ">=5.0" +[[package]] +name = "pytest-mock" +version = "3.14.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, + {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-snapshot" version = "0.9.0" @@ -495,4 +512,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "a8aef2d773dabdd18223fcb2aabce153b8a4f45750fe636d15acf5b47143ea16" +content-hash = "480d0af21c1df570785160169d0a7e89ed92a72f7b5cc4f35251540200a5eeaa" diff --git a/pyproject.toml b/pyproject.toml index 03dd7bcb..b7ecd5af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ pep8-naming = ">=0.11.1" pytest = ">=6.0.1" pytest_click = ">=1.0.2" pytest-snapshot = ">=0.6.3" - +pytest-mock = ">=3.8.1" [tool.poetry.scripts] ni-python-styleguide = 'ni_python_styleguide._cli:main' diff --git a/tests/conftest.py b/tests/conftest.py index c8fe912f..0130491b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,10 @@ """Useful plugins/fixtures which can (and should) be used in any test.""" +import builtins +import functools +import io import os +import pathlib import click.testing import pytest @@ -59,3 +63,48 @@ def chdir(): cwd = os.getcwd() yield os.chdir os.chdir(cwd) + +@pytest.fixture(autouse=True) +def fake_open(mocker): + """Mock open to prevent actual file system writes during tests.""" + original_open = builtins.open + # original_path_write = pathlib.Path.write_text + + def open_with_default_ascii(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if len(args) > 1: + mode = args[1] + else: + mode = kwargs.get('mode', 'r') + if 'b' not in mode and 'encoding' not in kwargs: + kwargs['encoding'] = 'ascii' + return func(*args, **kwargs) + + return wrapper + + original_io_open = io.open + + def io_open_with_default_encoding(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + # check for encoding in args + # self, mode, buffering, encoding, errors, newline + mode = kwargs.get('mode', 'r') + if len(args) > 1: + mode = args[1] + + if len(args) > 3: + encoding = args[3] + args = list(args[:3]) + for name, value in zip(['errors', 'newline'], args[4:]): + kwargs[name] = value + if 'encoding' not in kwargs and 'b' not in mode: + kwargs['encoding'] = 'ascii' + return func(*args, **kwargs) + + return wrapper + + _mock_open = mocker.patch("builtins.open", side_effect=open_with_default_ascii(original_open)) + _mock_path_write = mocker.patch("io.open", side_effect=io_open_with_default_encoding(original_io_open)) + yield None From 2ff4b631a32eef004aa065f14f119810fd0605b7 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:29 -0600 Subject: [PATCH 02/10] get fixture to actually work with help from copilot --- tests/conftest.py | 79 ++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0130491b..cee7f9e4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,6 @@ from ni_python_styleguide.__main__ import main as styleguide_main - def pytest_collection_modifyitems(items): """Ignores deprecation warnings in all tests.""" for item in items: @@ -64,47 +63,43 @@ def chdir(): yield os.chdir os.chdir(cwd) + @pytest.fixture(autouse=True) -def fake_open(mocker): - """Mock open to prevent actual file system writes during tests.""" +def force_ascii_encoding(monkeypatch): + """Force ASCII encoding as default for all file operations to catch missing encoding args.""" + import locale + + # Patch locale.getpreferredencoding to return 'ascii' + # This affects Path.read_text() and Path.write_text() default encoding + monkeypatch.setattr(locale, 'getpreferredencoding', lambda do_setlocale=True: 'ascii') + + # Patch builtins.open to use ASCII when encoding not specified original_open = builtins.open - # original_path_write = pathlib.Path.write_text - - def open_with_default_ascii(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - if len(args) > 1: - mode = args[1] - else: - mode = kwargs.get('mode', 'r') - if 'b' not in mode and 'encoding' not in kwargs: - kwargs['encoding'] = 'ascii' - return func(*args, **kwargs) - - return wrapper - - original_io_open = io.open - - def io_open_with_default_encoding(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - # check for encoding in args - # self, mode, buffering, encoding, errors, newline - mode = kwargs.get('mode', 'r') - if len(args) > 1: - mode = args[1] - - if len(args) > 3: - encoding = args[3] - args = list(args[:3]) - for name, value in zip(['errors', 'newline'], args[4:]): - kwargs[name] = value - if 'encoding' not in kwargs and 'b' not in mode: + + def ascii_open(*args, **kwargs): + if 'encoding' not in kwargs: + mode = args[1] if len(args) > 1 else kwargs.get('mode', 'r') + if 'b' not in mode: kwargs['encoding'] = 'ascii' - return func(*args, **kwargs) - - return wrapper - - _mock_open = mocker.patch("builtins.open", side_effect=open_with_default_ascii(original_open)) - _mock_path_write = mocker.patch("io.open", side_effect=io_open_with_default_encoding(original_io_open)) - yield None + return original_open(*args, **kwargs) + + monkeypatch.setattr(builtins, 'open', ascii_open) + + # Patch pathlib.Path.read_text and write_text to use ASCII when encoding not specified + original_read_text = pathlib.Path.read_text + original_write_text = pathlib.Path.write_text + + def ascii_read_text(self, encoding=None, errors=None): + if encoding is None: + encoding = 'ascii' + return original_read_text(self, encoding=encoding, errors=errors) + + def ascii_write_text(self, data, encoding=None, errors=None, newline=None): + if encoding is None: + encoding = 'ascii' + return original_write_text(self, data, encoding=encoding, errors=errors, newline=newline) + + monkeypatch.setattr(pathlib.Path, 'read_text', ascii_read_text) + monkeypatch.setattr(pathlib.Path, 'write_text', ascii_write_text) + + yield None \ No newline at end of file From 37f0df4c66b810919c6723a58a3d66e3296b6f40 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:32 -0600 Subject: [PATCH 03/10] make failing tests --- .../unicode_in_files/input.py | 10 ++++++ .../unicode_in_files/output.py.txt | 10 ++++++ .../unicode_in_files/output__aggressive.py | 12 +++++++ .../unicode_example/input.py | 29 +++++++++++++++++ .../unicode_example/output.py.txt | 31 +++++++++++++++++++ .../file_with_unicode/input.py | 29 +++++++++++++++++ .../file_with_unicode/output.py | 31 +++++++++++++++++++ tests/test_cli/test_format.py | 2 +- 8 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 tests/test_cli/fix_test_cases__snapshots/unicode_example/input.py create mode 100644 tests/test_cli/fix_test_cases__snapshots/unicode_example/output.py.txt create mode 100644 tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py create mode 100644 tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py diff --git a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/input.py b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/input.py index b8cb656d..157b0d82 100644 --- a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/input.py +++ b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/input.py @@ -17,3 +17,13 @@ def problem_chars(self): def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): """Provide example where black will want to split out result.""" return 5 + 7 + +data = ("device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) \ No newline at end of file diff --git a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output.py.txt b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output.py.txt index 5aa604a2..230514f5 100644 --- a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output.py.txt +++ b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output.py.txt @@ -17,3 +17,13 @@ class Foo: def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): # noqa: N803, N802 - argument name 'myBadlyNamedParam' should be lowercase (auto-generated noqa), function name 'method_withBadName_andParams' should be lowercase (auto-generated noqa) """Provide example where black will want to split out result.""" return 5 + 7 + +data = ("device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) \ No newline at end of file diff --git a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output__aggressive.py b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output__aggressive.py index 68a68cf4..550b7d72 100644 --- a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output__aggressive.py +++ b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/unicode_in_files/output__aggressive.py @@ -21,3 +21,15 @@ def method_withBadName_andParams( # noqa: N802 - function name 'method_withBadN ): """Provide example where black will want to split out result.""" return 5 + 7 + + +data = ( + "device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) diff --git a/tests/test_cli/fix_test_cases__snapshots/unicode_example/input.py b/tests/test_cli/fix_test_cases__snapshots/unicode_example/input.py new file mode 100644 index 00000000..157b0d82 --- /dev/null +++ b/tests/test_cli/fix_test_cases__snapshots/unicode_example/input.py @@ -0,0 +1,29 @@ +"""Unicode in file should not cause error (e.g., ©).""" + + +class Foo: + """Example class with unicode consts.""" + + def __init__(self) -> None: + """Instantiate Foo class.""" + self._problem_chars = "π”" + + @property + def problem_chars(self): + """Return stored string with a unicode char.""" + return self._problem_chars + + +def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): + """Provide example where black will want to split out result.""" + return 5 + 7 + +data = ("device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) \ No newline at end of file diff --git a/tests/test_cli/fix_test_cases__snapshots/unicode_example/output.py.txt b/tests/test_cli/fix_test_cases__snapshots/unicode_example/output.py.txt new file mode 100644 index 00000000..ea2d139d --- /dev/null +++ b/tests/test_cli/fix_test_cases__snapshots/unicode_example/output.py.txt @@ -0,0 +1,31 @@ +"""Unicode in file should not cause error (e.g., ©).""" + + +class Foo: + """Example class with unicode consts.""" + + def __init__(self) -> None: + """Instantiate Foo class.""" + self._problem_chars = "π”" + + @property + def problem_chars(self): + """Return stored string with a unicode char.""" + return self._problem_chars + + +def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): + """Provide example where black will want to split out result.""" + return 5 + 7 + + +data = ( + "device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) diff --git a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py new file mode 100644 index 00000000..157b0d82 --- /dev/null +++ b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py @@ -0,0 +1,29 @@ +"""Unicode in file should not cause error (e.g., ©).""" + + +class Foo: + """Example class with unicode consts.""" + + def __init__(self) -> None: + """Instantiate Foo class.""" + self._problem_chars = "π”" + + @property + def problem_chars(self): + """Return stored string with a unicode char.""" + return self._problem_chars + + +def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): + """Provide example where black will want to split out result.""" + return 5 + 7 + +data = ("device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) \ No newline at end of file diff --git a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py new file mode 100644 index 00000000..ea2d139d --- /dev/null +++ b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py @@ -0,0 +1,31 @@ +"""Unicode in file should not cause error (e.g., ©).""" + + +class Foo: + """Example class with unicode consts.""" + + def __init__(self) -> None: + """Instantiate Foo class.""" + self._problem_chars = "π”" + + @property + def problem_chars(self): + """Return stored string with a unicode char.""" + return self._problem_chars + + +def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): + """Provide example where black will want to split out result.""" + return 5 + 7 + + +data = ( + "device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) diff --git a/tests/test_cli/test_format.py b/tests/test_cli/test_format.py index dad47b67..d56509f6 100644 --- a/tests/test_cli/test_format.py +++ b/tests/test_cli/test_format.py @@ -24,7 +24,7 @@ def test_given_input_file__produces_expected_output_simple( output = styleguide_command(command="format") assert output.exit_code in (True, 0), f"Error in running:\n{output}" - result = test_file.read_text() + result = test_file.read_text(encoding="utf-8") snapshot.snapshot_dir = test_dir snapshot.assert_match(result, "output.py") From 96be8e043337dabaaf39de34792e1adf191365c6 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:37 -0600 Subject: [PATCH 04/10] make it pass --- ni_python_styleguide/_fix.py | 10 +++--- ni_python_styleguide/_utils/code_analysis.py | 2 +- .../unicode_example/output__aggressive.py | 35 +++++++++++++++++++ .../file_with_unicode/output.diff | 14 ++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 tests/test_cli/fix_test_cases__snapshots/unicode_example/output__aggressive.py create mode 100644 tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff diff --git a/ni_python_styleguide/_fix.py b/ni_python_styleguide/_fix.py index 332ee341..dd0d8db3 100644 --- a/ni_python_styleguide/_fix.py +++ b/ni_python_styleguide/_fix.py @@ -21,7 +21,7 @@ def _sort_imports(file: pathlib.Path, app_import_names): - raw = file.read_text() + raw = file.read_text(encoding="utf-8") isort_config = isort.Config( settings_file=str(_config_constants.ISORT_CONFIG_FILE), known_first_party=filter(None, app_import_names.split(",")), @@ -30,7 +30,7 @@ def _sort_imports(file: pathlib.Path, app_import_names): raw, config=isort_config, ) - file.write_text(output) + file.write_text(output, encoding="utf-8") def _format_imports(file: pathlib.Path, app_import_names: Iterable[str]) -> None: @@ -117,13 +117,13 @@ def fix( ) else: with temp_file.multi_access_tempfile(suffix="__" + bad_file.name) as working_file: - working_file.write_text(bad_file.read_text()) + working_file.write_text(bad_file.read_text(encoding="utf-8"), encoding="utf-8") _format.format(working_file, "-q") _format_imports(file=working_file, app_import_names=app_import_names) diff_lines = better_diff.unified_plus.format_diff( - bad_file.read_text(), - working_file.read_text(), + bad_file.read_text(encoding="utf-8"), + working_file.read_text(encoding="utf-8"), fromfile=f"{_posix_relative_if_under(bad_file, pathlib.Path.cwd())}", tofile=f"{_posix_relative_if_under(bad_file, pathlib.Path.cwd())}_formatted", ) diff --git a/ni_python_styleguide/_utils/code_analysis.py b/ni_python_styleguide/_utils/code_analysis.py index 7c9faabe..5fcaaaeb 100644 --- a/ni_python_styleguide/_utils/code_analysis.py +++ b/ni_python_styleguide/_utils/code_analysis.py @@ -11,7 +11,7 @@ def find_import_region(file: pathlib.Path) -> Tuple[int, int]: file: pathlib.Path path to file to evaluate Return: Tuple[int, int] the start and ending lines (0 based) of the module level imports """ - file_contents = file.read_text() + file_contents = file.read_text(encoding="utf-8") tree = ast.parse(file_contents) end = start = 0 for node in tree.body: # only walk top level items diff --git a/tests/test_cli/fix_test_cases__snapshots/unicode_example/output__aggressive.py b/tests/test_cli/fix_test_cases__snapshots/unicode_example/output__aggressive.py new file mode 100644 index 00000000..550b7d72 --- /dev/null +++ b/tests/test_cli/fix_test_cases__snapshots/unicode_example/output__aggressive.py @@ -0,0 +1,35 @@ +"""Unicode in file should not cause error (e.g., ©).""" + + +class Foo: + """Example class with unicode consts.""" + + def __init__(self) -> None: + """Instantiate Foo class.""" + self._problem_chars = "π”" + + @property + def problem_chars(self): + """Return stored string with a unicode char.""" + return self._problem_chars + + +def method_withBadName_andParams( # noqa: N802 - function name 'method_withBadName_andParams' should be lowercase (auto-generated noqa) + my_normal_param, + myBadlyNamedParam, # noqa: N803 - argument name 'myBadlyNamedParam' should be lowercase (auto-generated noqa) + my_other_Bad_param, # noqa: N803 - argument name 'my_other_Bad_param' should be lowercase (auto-generated noqa) +): + """Provide example where black will want to split out result.""" + return 5 + 7 + + +data = ( + "device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), + ("デバイス", ["932", "shift-jis", "utf-8"]), + ("장치", ["utf-8", "euc-kr"]), + ("设备", ["utf-8", "gbk"]), + ], +) diff --git a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff new file mode 100644 index 00000000..218c940a --- /dev/null +++ b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff @@ -0,0 +1,14 @@ +--- input.py ++++ input.py_formatted +@@ -18,7 +18,9 @@ + """Provide example where black will want to split out result.""" + return 5 + 7 + +-data = ("device_name, supported_encodings", ++ ++data = ( ++ "device_name, supported_encodings", + [ + ("Gerät", ["1252", "iso-8859-1", "utf-8"]), + ("l' appareil", ["1252", "iso-8859-1", "utf-8"]), +\ No newline at end of file (input.py) \ No newline at end of file From d22e9c52c56a3b84eedccffe4b2c02a1ed1620c5 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:43 -0600 Subject: [PATCH 05/10] simplify fixture setup --- tests/conftest.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cee7f9e4..84d4eb4c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,5 @@ """Useful plugins/fixtures which can (and should) be used in any test.""" -import builtins -import functools -import io import os import pathlib @@ -67,24 +64,6 @@ def chdir(): @pytest.fixture(autouse=True) def force_ascii_encoding(monkeypatch): """Force ASCII encoding as default for all file operations to catch missing encoding args.""" - import locale - - # Patch locale.getpreferredencoding to return 'ascii' - # This affects Path.read_text() and Path.write_text() default encoding - monkeypatch.setattr(locale, 'getpreferredencoding', lambda do_setlocale=True: 'ascii') - - # Patch builtins.open to use ASCII when encoding not specified - original_open = builtins.open - - def ascii_open(*args, **kwargs): - if 'encoding' not in kwargs: - mode = args[1] if len(args) > 1 else kwargs.get('mode', 'r') - if 'b' not in mode: - kwargs['encoding'] = 'ascii' - return original_open(*args, **kwargs) - - monkeypatch.setattr(builtins, 'open', ascii_open) - # Patch pathlib.Path.read_text and write_text to use ASCII when encoding not specified original_read_text = pathlib.Path.read_text original_write_text = pathlib.Path.write_text @@ -102,4 +81,4 @@ def ascii_write_text(self, data, encoding=None, errors=None, newline=None): monkeypatch.setattr(pathlib.Path, 'read_text', ascii_read_text) monkeypatch.setattr(pathlib.Path, 'write_text', ascii_write_text) - yield None \ No newline at end of file + yield None From 2b3349a9ed535f6bf6ad0ccc4a8a240e2aa0612e Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:46 -0600 Subject: [PATCH 06/10] format doesn't fix method names, so make output be able to pass --- .../format_test_cases__snapshots/file_with_unicode/input.py | 4 ++-- .../file_with_unicode/output.diff | 2 +- .../format_test_cases__snapshots/file_with_unicode/output.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py index 157b0d82..ba2c5364 100644 --- a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py +++ b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/input.py @@ -14,8 +14,8 @@ def problem_chars(self): return self._problem_chars -def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): - """Provide example where black will want to split out result.""" +def method_with_valid_name(my_normal_param): + """Provide with a short, valid name.""" return 5 + 7 data = ("device_name, supported_encodings", diff --git a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff index 218c940a..ed162038 100644 --- a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff +++ b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.diff @@ -1,7 +1,7 @@ --- input.py +++ input.py_formatted @@ -18,7 +18,9 @@ - """Provide example where black will want to split out result.""" + """Provide with a short, valid name.""" return 5 + 7 -data = ("device_name, supported_encodings", diff --git a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py index ea2d139d..09c95dea 100644 --- a/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py +++ b/tests/test_cli/format_test_cases__snapshots/file_with_unicode/output.py @@ -14,8 +14,8 @@ def problem_chars(self): return self._problem_chars -def method_withBadName_andParams(my_normal_param, myBadlyNamedParam, my_other_Bad_param): - """Provide example where black will want to split out result.""" +def method_with_valid_name(my_normal_param): + """Provide with a short, valid name.""" return 5 + 7 From 809acbd6e0d9f5c4f82dc7205d7cf6b979db2fe0 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:50 -0600 Subject: [PATCH 07/10] clean up changes some --- ni_python_styleguide/_fix.py | 11 ++++++----- ni_python_styleguide/_utils/code_analysis.py | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ni_python_styleguide/_fix.py b/ni_python_styleguide/_fix.py index dd0d8db3..f039790d 100644 --- a/ni_python_styleguide/_fix.py +++ b/ni_python_styleguide/_fix.py @@ -1,5 +1,6 @@ import logging import pathlib +import shutil import typing from collections import defaultdict from typing import Iterable @@ -21,7 +22,7 @@ def _sort_imports(file: pathlib.Path, app_import_names): - raw = file.read_text(encoding="utf-8") + raw = file.read_text(encoding=_utils.DEFAULT_ENCODING) isort_config = isort.Config( settings_file=str(_config_constants.ISORT_CONFIG_FILE), known_first_party=filter(None, app_import_names.split(",")), @@ -30,7 +31,7 @@ def _sort_imports(file: pathlib.Path, app_import_names): raw, config=isort_config, ) - file.write_text(output, encoding="utf-8") + file.write_text(output, encoding=_utils.DEFAULT_ENCODING) def _format_imports(file: pathlib.Path, app_import_names: Iterable[str]) -> None: @@ -117,13 +118,13 @@ def fix( ) else: with temp_file.multi_access_tempfile(suffix="__" + bad_file.name) as working_file: - working_file.write_text(bad_file.read_text(encoding="utf-8"), encoding="utf-8") + shutil.copyfile(bad_file, working_file) _format.format(working_file, "-q") _format_imports(file=working_file, app_import_names=app_import_names) diff_lines = better_diff.unified_plus.format_diff( - bad_file.read_text(encoding="utf-8"), - working_file.read_text(encoding="utf-8"), + bad_file.read_text(encoding=_utils.DEFAULT_ENCODING), + working_file.read_text(encoding=_utils.DEFAULT_ENCODING), fromfile=f"{_posix_relative_if_under(bad_file, pathlib.Path.cwd())}", tofile=f"{_posix_relative_if_under(bad_file, pathlib.Path.cwd())}_formatted", ) diff --git a/ni_python_styleguide/_utils/code_analysis.py b/ni_python_styleguide/_utils/code_analysis.py index 5fcaaaeb..25e1c9cb 100644 --- a/ni_python_styleguide/_utils/code_analysis.py +++ b/ni_python_styleguide/_utils/code_analysis.py @@ -11,7 +11,9 @@ def find_import_region(file: pathlib.Path) -> Tuple[int, int]: file: pathlib.Path path to file to evaluate Return: Tuple[int, int] the start and ending lines (0 based) of the module level imports """ - file_contents = file.read_text(encoding="utf-8") + file_contents = file.read_text( + encoding="utf-8" + ) # can't use DEFAULT_ENCODING here due to possible circular imports tree = ast.parse(file_contents) end = start = 0 for node in tree.body: # only walk top level items From 593db5030d48d3c2a1087dc49a76fef0b180c6e8 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:41:53 -0600 Subject: [PATCH 08/10] format --- tests/conftest.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 84d4eb4c..fd6c02d0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ from ni_python_styleguide.__main__ import main as styleguide_main + def pytest_collection_modifyitems(items): """Ignores deprecation warnings in all tests.""" for item in items: @@ -67,18 +68,18 @@ def force_ascii_encoding(monkeypatch): # Patch pathlib.Path.read_text and write_text to use ASCII when encoding not specified original_read_text = pathlib.Path.read_text original_write_text = pathlib.Path.write_text - + def ascii_read_text(self, encoding=None, errors=None): if encoding is None: - encoding = 'ascii' + encoding = "ascii" return original_read_text(self, encoding=encoding, errors=errors) - + def ascii_write_text(self, data, encoding=None, errors=None, newline=None): if encoding is None: - encoding = 'ascii' + encoding = "ascii" return original_write_text(self, data, encoding=encoding, errors=errors, newline=newline) - - monkeypatch.setattr(pathlib.Path, 'read_text', ascii_read_text) - monkeypatch.setattr(pathlib.Path, 'write_text', ascii_write_text) + + monkeypatch.setattr(pathlib.Path, "read_text", ascii_read_text) + monkeypatch.setattr(pathlib.Path, "write_text", ascii_write_text) yield None From 519ee162ec675dcfa0b0079a24a9784a52a485dc Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:48:35 -0600 Subject: [PATCH 09/10] undo adding pytest-mock, not needed yet --- poetry.lock | 21 ++------------------- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 626cfb73..f7bd279f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "better-diff" @@ -419,23 +419,6 @@ files = [ click = ">=6.0" pytest = ">=5.0" -[[package]] -name = "pytest-mock" -version = "3.14.1" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, - {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - [[package]] name = "pytest-snapshot" version = "0.9.0" @@ -512,4 +495,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "480d0af21c1df570785160169d0a7e89ed92a72f7b5cc4f35251540200a5eeaa" +content-hash = "a8aef2d773dabdd18223fcb2aabce153b8a4f45750fe636d15acf5b47143ea16" diff --git a/pyproject.toml b/pyproject.toml index b7ecd5af..f8dbaa9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ pep8-naming = ">=0.11.1" pytest = ">=6.0.1" pytest_click = ">=1.0.2" pytest-snapshot = ">=0.6.3" -pytest-mock = ">=3.8.1" [tool.poetry.scripts] ni-python-styleguide = 'ni_python_styleguide._cli:main' From acb8ccae995d49b855b9203f5911e9a2bf8acc26 Mon Sep 17 00:00:00 2001 From: mshafer-NI <23644905+mshafer-NI@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:57:32 -0600 Subject: [PATCH 10/10] Apply suggestions from code review --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fd6c02d0..f8180add 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,10 +74,10 @@ def ascii_read_text(self, encoding=None, errors=None): encoding = "ascii" return original_read_text(self, encoding=encoding, errors=errors) - def ascii_write_text(self, data, encoding=None, errors=None, newline=None): + def ascii_write_text(self, data, encoding=None, errors=None, **kwargs): if encoding is None: encoding = "ascii" - return original_write_text(self, data, encoding=encoding, errors=errors, newline=newline) + return original_write_text(self, data, encoding=encoding, errors=errors, **kwargs) monkeypatch.setattr(pathlib.Path, "read_text", ascii_read_text) monkeypatch.setattr(pathlib.Path, "write_text", ascii_write_text)