mycli fails to build with Python 3.13.0a5. + /usr/bin/pytest ============================= test session starts ============================== platform linux -- Python 3.13.0a5, pytest-7.4.3, pluggy-1.3.0 rootdir: /builddir/build/BUILD/mycli-1.27.0 configfile: pytest.ini collected 276 items test/test_clistyle.py ss [ 0%] test/test_completion_engine.py ......................................... [ 15%] ...................x................................................... [ 41%] test/test_completion_refresher.py .... [ 42%] test/test_config.py .............. [ 47%] test/test_dbspecial.py .... [ 49%] test/test_main.py ssssssF.FFFFFFF..ss INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/main.py", line 271, in wrap_session INTERNALERROR> session.exitstatus = doit(config, session) or 0 INTERNALERROR> ~~~~^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/main.py", line 325, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_hooks.py", line 493, in __call__ INTERNALERROR> return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_manager.py", line 115, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 152, in _multicall INTERNALERROR> return outcome.get_result() INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_result.py", line 114, in get_result INTERNALERROR> raise exc.with_traceback(exc.__traceback__) INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 77, in _multicall INTERNALERROR> res = hook_impl.function(*args) INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/main.py", line 350, in pytest_runtestloop INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_hooks.py", line 493, in __call__ INTERNALERROR> return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_manager.py", line 115, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 152, in _multicall INTERNALERROR> return outcome.get_result() INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_result.py", line 114, in get_result INTERNALERROR> raise exc.with_traceback(exc.__traceback__) INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 77, in _multicall INTERNALERROR> res = hook_impl.function(*args) INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/runner.py", line 113, in pytest_runtest_protocol INTERNALERROR> ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_hooks.py", line 493, in __call__ INTERNALERROR> return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_manager.py", line 115, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 113, in _multicall INTERNALERROR> raise exception.with_traceback(exception.__traceback__) INTERNALERROR> File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 77, in _multicall INTERNALERROR> res = hook_impl.function(*args) INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/terminal.py", line 566, in pytest_runtest_logstart INTERNALERROR> self.write_fspath_result(nodeid, "") INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/terminal.py", line 430, in write_fspath_result INTERNALERROR> self._write_progress_information_filling_space() INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/terminal.py", line 684, in _write_progress_information_filling_space INTERNALERROR> fill = self._tw.fullwidth - w - 1 INTERNALERROR> ^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/_io/terminalwriter.py", line 86, in fullwidth INTERNALERROR> return get_terminal_width() INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^ INTERNALERROR> File "/usr/lib/python3.13/site-packages/_pytest/_io/terminalwriter.py", line 17, in get_terminal_width INTERNALERROR> width, _ = shutil.get_terminal_size(fallback=(80, 24)) INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^ INTERNALERROR> TypeError: test_reserved_space_is_integer.<locals>.stub_terminal_size() got an unexpected keyword argument 'fallback' Traceback (most recent call last): File "/usr/bin/pytest", line 8, in <module> sys.exit(console_main()) ~~~~~~~~~~~~^^ File "/usr/lib/python3.13/site-packages/_pytest/config/__init__.py", line 192, in console_main code = main() ~~~~^^ File "/usr/lib/python3.13/site-packages/_pytest/config/__init__.py", line 169, in main ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ config=config ^^^^^^^^^^^^^ ) ^ File "/usr/lib/python3.13/site-packages/pluggy/_hooks.py", line 493, in __call__ return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/site-packages/pluggy/_manager.py", line 115, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 113, in _multicall raise exception.with_traceback(exception.__traceback__) File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 77, in _multicall res = hook_impl.function(*args) ~~~~~~~~~~~~~~~~~~^^^^^^^ File "/usr/lib/python3.13/site-packages/_pytest/main.py", line 318, in pytest_cmdline_main return wrap_session(config, _main) ~~~~~~~~~~~~^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/site-packages/_pytest/main.py", line 306, in wrap_session config.hook.pytest_sessionfinish( ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ session=session, exitstatus=session.exitstatus ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/usr/lib/python3.13/site-packages/pluggy/_hooks.py", line 493, in __call__ return self._hookexec(self.name, self._hookimpls, kwargs, firstresult) ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/site-packages/pluggy/_manager.py", line 115, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/site-packages/pluggy/_callers.py", line 130, in _multicall teardown[0].send(outcome) ~~~~~~~~~~~~~~~~^^^^^^^^^ File "/usr/lib/python3.13/site-packages/_pytest/terminal.py", line 877, in pytest_sessionfinish self.summary_stats() ~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/site-packages/_pytest/terminal.py", line 1105, in summary_stats fullwidth = self._tw.fullwidth ^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.13/site-packages/_pytest/_io/terminalwriter.py", line 86, in fullwidth return get_terminal_width() ~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.13/site-packages/_pytest/_io/terminalwriter.py", line 17, in get_terminal_width width, _ = shutil.get_terminal_size(fallback=(80, 24)) ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^ TypeError: test_reserved_space_is_integer.<locals>.stub_terminal_size() got an unexpected keyword argument 'fallback' https://docs.python.org/3.13/whatsnew/3.13.html For the build logs, see: https://copr-be.cloud.fedoraproject.org/results/@python/python3.13/fedora-rawhide-x86_64/07211141-mycli/ For all our attempts to build mycli with Python 3.13, see: https://copr.fedorainfracloud.org/coprs/g/python/python3.13/package/mycli/ Testing and mass rebuild of packages is happening in copr. You can follow these instructions to test locally in mock if your package builds with Python 3.13: https://copr.fedorainfracloud.org/coprs/g/python/python3.13/ Let us know here if you have any questions. Python 3.13 is planned to be included in Fedora 41. To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.13. A build failure prevents us from testing all dependent packages (transitive [Build]Requires), so if this package is required a lot, it's important for us to get it fixed soon. We'd appreciate help from the people who know this package best, but if you don't want to work on this now, let us know so we can try to work around it on our side.
Docs: https://docs.python.org/3.13/library/shutil.html still say: ----- shutil.get_terminal_size(fallback=(columns, lines)) Get the size of the terminal window. For each of the two dimensions, the environment variable, COLUMNS and LINES respectively, is checked. If the variable is defined and the value is a positive integer, it is used. When COLUMNS or LINES is not defined, which is the common case, the terminal connected to sys.__stdout__ is queried by invoking os.get_terminal_size(). If the terminal size cannot be successfully queried, either because the system doesn’t support querying, or because we are not connected to a terminal, the value given in fallback parameter is used. fallback defaults to (80, 24) which is the default size used by many terminal emulators. ----- Any link to document fallback is gone in 3.13?
It looks like pytest tries to execute `shutil.get_terminal_size` while it's being replaced by the test stub: https://github.com/dbcli/mycli/blob/main/test/test_main.py#L264 What's unclear to me is why it started to behave like that with Python 3.13 (I tested it in Copr with the current Rawhide and it builds, the same with Koschei builds). pytest version is the same in both repositories.
If I force the test to fail, like this: --- a/test/test_main.py +++ b/test/test_main.py @@ -264,6 +264,8 @@ def test_reserved_space_is_integer(): shutil.get_terminal_size = stub_terminal_size mycli = MyCli() assert isinstance(mycli.get_reserved_space(), int) + + assert False shutil.get_terminal_size = old_func I get the same problem on Python 3.12: INTERNALERROR> TypeError: test_reserved_space_is_integer.<locals>.stub_terminal_size() got an unexpected keyword argument 'fallback' ------------- If I put the unstubbing into finally, like this: --- a/test/test_main.py +++ b/test/test_main.py @@ -262,10 +262,11 @@ def test_reserved_space_is_integer(): old_func = shutil.get_terminal_size shutil.get_terminal_size = stub_terminal_size - mycli = MyCli() - assert isinstance(mycli.get_reserved_space(), int) - - shutil.get_terminal_size = old_func + try: + mycli = MyCli() + assert isinstance(mycli.get_reserved_space(), int) + finally: + shutil.get_terminal_size = old_func I see the actual Python 3.13 test failures: =================================== FAILURES =================================== ___________________________ test_thanks_picker_utf8 ____________________________ def test_thanks_picker_utf8(): > name = thanks_picker() test/test_main.py:145: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ def thanks_picker(): import mycli lines = ( > resources.read_text(mycli, 'AUTHORS') + resources.read_text(mycli, 'SPONSORS') ).split('\n') E AttributeError: module 'importlib.resources' has no attribute 'read_text' mycli/main.py:1431: AttributeError __________________ test_command_descriptions_end_with_periods __________________ def test_command_descriptions_end_with_periods(): """Make sure that mycli commands' descriptions end with a period.""" > MyCli() test/test_main.py:159: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ mycli/main.py:139: in __init__ c = self.config = read_config_files(config_files) mycli/config.py:101: in read_config_files config = create_default_config(list_values=list_values) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ list_values = True def create_default_config(list_values=True): import mycli > default_config_file = resources.open_text(mycli, 'myclirc') E AttributeError: module 'importlib.resources' has no attribute 'open_text' mycli/config.py:120: AttributeError ____________________________ test_conditional_pager ____________________________ monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f5f2a6b3830> def test_conditional_pager(monkeypatch): testdata = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do".split( " ") # User didn't set pager, output doesn't fit screen -> pager > output( monkeypatch, terminal_size=(5, 10), testdata=testdata, explicit_pager=False, expect_pager=True ) test/test_main.py:214: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ test/test_main.py:167: in output m = MyCli(myclirc=default_config_file) mycli/main.py:139: in __init__ c = self.config = read_config_files(config_files) mycli/config.py:101: in read_config_files config = create_default_config(list_values=list_values) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ list_values = True def create_default_config(list_values=True): import mycli > default_config_file = resources.open_text(mycli, 'myclirc') E AttributeError: module 'importlib.resources' has no attribute 'open_text' mycli/config.py:120: AttributeError ________________________ test_reserved_space_is_integer ________________________ def test_reserved_space_is_integer(): """Make sure that reserved space is returned as an integer.""" def stub_terminal_size(): return (5, 5) old_func = shutil.get_terminal_size shutil.get_terminal_size = stub_terminal_size try: > mycli = MyCli() test/test_main.py:266: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ mycli/main.py:139: in __init__ c = self.config = read_config_files(config_files) mycli/config.py:101: in read_config_files config = create_default_config(list_values=list_values) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ list_values = True def create_default_config(list_values=True): import mycli > default_config_file = resources.open_text(mycli, 'myclirc') E AttributeError: module 'importlib.resources' has no attribute 'open_text' mycli/config.py:120: AttributeError ________________________________ test_list_dsn _________________________________ def test_list_dsn(): runner = CliRunner() with NamedTemporaryFile(mode="w") as myclirc: myclirc.write(dedent("""\ [alias_dsn] test = mysql://test/test """)) myclirc.flush() args = ['--list-dsn', '--myclirc', myclirc.name] result = runner.invoke(cli, args=args) > assert result.output == "test\n" E AssertionError: assert '' == 'test\n' E - test test/test_main.py:282: AssertionError ___________________________ test_prettify_statement ____________________________ def test_prettify_statement(): statement = 'SELECT 1' > m = MyCli() test/test_main.py:289: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ mycli/main.py:139: in __init__ c = self.config = read_config_files(config_files) mycli/config.py:101: in read_config_files config = create_default_config(list_values=list_values) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ list_values = True def create_default_config(list_values=True): import mycli > default_config_file = resources.open_text(mycli, 'myclirc') E AttributeError: module 'importlib.resources' has no attribute 'open_text' mycli/config.py:120: AttributeError __________________________ test_unprettify_statement ___________________________ def test_unprettify_statement(): statement = 'SELECT\n 1' > m = MyCli() test/test_main.py:296: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ mycli/main.py:139: in __init__ c = self.config = read_config_files(config_files) mycli/config.py:101: in read_config_files config = create_default_config(list_values=list_values) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ list_values = True def create_default_config(list_values=True): import mycli > default_config_file = resources.open_text(mycli, 'myclirc') E AttributeError: module 'importlib.resources' has no attribute 'open_text' mycli/config.py:120: AttributeError _____________________________ test_list_ssh_config _____________________________ def test_list_ssh_config(): runner = CliRunner() with NamedTemporaryFile(mode="w") as ssh_config: ssh_config.write(dedent("""\ Host test Hostname test.example.com User joe Port 22222 IdentityFile ~/.ssh/gateway """)) ssh_config.flush() args = ['--list-ssh-config', '--ssh-config-path', ssh_config.name] result = runner.invoke(cli, args=args) > assert "test\n" in result.output E assert 'test\n' in '' E + where '' = <Result AttributeError("module 'importlib.resources' has no attribute 'open_text'")>.output test/test_main.py:314: AssertionError =========================== short test summary info ============================ FAILED test/test_main.py::test_thanks_picker_utf8 - AttributeError: module 'i... FAILED test/test_main.py::test_command_descriptions_end_with_periods - Attrib... FAILED test/test_main.py::test_conditional_pager - AttributeError: module 'im... FAILED test/test_main.py::test_reserved_space_is_integer - AttributeError: mo... FAILED test/test_main.py::test_list_dsn - AssertionError: assert '' == 'test\n' FAILED test/test_main.py::test_prettify_statement - AttributeError: module 'i... FAILED test/test_main.py::test_unprettify_statement - AttributeError: module ... FAILED test/test_main.py::test_list_ssh_config - assert 'test\n' in '' ======= 8 failed, 225 passed, 41 skipped, 1 xfailed, 1 xpassed in 4.62s ======== tl;dr The test does not handle problems very well.
Upstream PR: https://github.com/dbcli/mycli/pull/1152
Thanks Miro! Karolina, can you check with mycli 1.27.1 now available in rawhide, this version includes fix by Miro.
Note that my fix does not fix the Python 3.13 problem, it only makes the error more obvious: https://copr.fedorainfracloud.org/coprs/g/python/python3.13/build/7242854/ def create_default_config(list_values=True): import mycli > default_config_file = resources.open_text(mycli, 'myclirc') E AttributeError: module 'importlib.resources' has no attribute 'open_text' FAILED test/test_main.py::test_thanks_picker_utf8 - AttributeError: module 'i... FAILED test/test_main.py::test_command_descriptions_end_with_periods - Attrib... FAILED test/test_main.py::test_conditional_pager - AttributeError: module 'im... FAILED test/test_main.py::test_reserved_space_is_integer - AttributeError: mo... FAILED test/test_main.py::test_list_dsn - AssertionError: assert '' == 'test\n' FAILED test/test_main.py::test_prettify_statement - AttributeError: module 'i... FAILED test/test_main.py::test_unprettify_statement - AttributeError: module ... FAILED test/test_main.py::test_list_ssh_config - assert 'test\n' in ''
Removed usage of legacy resources features.