Skip to content

[wip] python 3.13#68927

Closed
dwoz wants to merge 52 commits intosaltstack:masterfrom
dwoz:pyversion313
Closed

[wip] python 3.13#68927
dwoz wants to merge 52 commits intosaltstack:masterfrom
dwoz:pyversion313

Conversation

@dwoz
Copy link
Copy Markdown
Contributor

@dwoz dwoz commented Apr 13, 2026

What does this PR do?

What issues does this PR fix or reference?

Fixes

Previous Behavior

Remove this section if not relevant

New Behavior

Remove this section if not relevant

Merge requirements satisfied?

[NOTICE] Bug fixes or features added to Salt require tests.

Commits signed with GPG?

Yes/No

dwoz and others added 30 commits April 11, 2026 21:55
The setuptools-scm 10.0.2 sdist contains a pyproject.toml with a
backend-path entry (../vcs-versioning/src) that points outside its own
source tree — a developer's local monorepo path that was accidentally
shipped in the release. pip 25.2 now validates that backend-path entries
stay inside the source tree and rejects it. The wheel is fine since it
bypasses pyproject.toml processing entirely.
`lxml` → requirements/base.txt: added lxml>=5.0.0; sys_platform != 'win32' (no cp312 wheels before 5.0.0)
`ncclient` → requirements/static/ci/common.in: added ncclient>=0.6.16; sys_platform != 'win32' (SafeConfigParser removed in Python 3.12)
`pygit2` → requirements/static/ci/{linux,darwin,windows}.in: bumped >=1.10.1 → >=1.14.0 (no cp312 wheels before 1.14.0)
…Subset, distutils, and utcnow

Agent-Logs-Url: https://github.com/saltstack/salt/sessions/8d432548-1f18-44b2-9fea-496226ca1f79

Co-authored-by: dwoz <1527763+dwoz@users.noreply.github.com>
…ypp_plugins.py, test_pip.py)

Co-authored-by: dwoz <1527763+dwoz@users.noreply.github.com>
- tests/unit/test_zypp_plugins.py: replace `import imp` (removed in
  Python 3.12) with `importlib.util`, and replace `imp.load_source()`
  with `spec_from_file_location` + `module_from_spec` + `exec_module`

- salt/modules/virtualenv_mod.py: fix `get_distribution_path` to use
  `importlib.metadata` instead of `pkg_resources`; old setuptools
  references `pkgutil.ImpImporter` which was removed in Python 3.12

- salt/utils/versions.py: replace `datetime.utcnow()` (deprecated in
  Python 3.12) with `datetime.now(datetime.timezone.utc)` to eliminate
  the DeprecationWarning that breaks test_deprecation_warnings[env0-False]

Agent-Logs-Url: https://github.com/saltstack/salt/sessions/80a13379-dae2-4c51-bff3-d9e06c170f4b

Co-authored-by: dwoz <1527763+dwoz@users.noreply.github.com>
Replace deprecated datetime.utcnow() calls with a compatibility
function that uses datetime.now(timezone.utc) for Python 3.12+
and falls back to datetime.utcnow() for older versions.

Also fix zeromq test that failed due to accessing uninitialized
socket object by calling _init_socket() before socket access.

Changes:
- Add salt.utils.timeutil.utcnow() compatibility function
- Replace all datetime.utcnow() calls across salt modules
- Fix test_request_client_send_recv_loop_closed socket initialization
Use named argument format to avoid duplicate 'distribution' parameter
in string formatting.
Replace datetime.datetime.utcnow() calls in transport_ssl.py test
fixtures with salt.utils.timeutil.utcnow() to avoid deprecation
warnings in Python 3.12+.
Replace remaining datetime.utcnow() calls in test files with
salt.utils.timeutil.utcnow() to avoid deprecation errors when
RAISE_DEPRECATIONS_RUNTIME_ERRORS=1 in Python 3.12+.

Fixed files:
- tests/pytests/functional/modules/test_system.py (8 occurrences)
- tests/pytests/unit/test_fileserver.py (1 occurrence)
- tests/pytests/unit/utils/test_aws.py (7 occurrences)
- tests/unit/modules/test_x509.py (3 occurrences)
In Python 3.12, importlib.util.spec_from_file_location() returns None
for files without .py extension when it cannot infer the loader.
The zyppnotify file lacks a .py extension, causing the spec to be None
and resulting in AttributeError when trying to access spec.loader.

Fix by explicitly using importlib.machinery.SourceFileLoader to create
the spec, which works regardless of file extension.
1. Fix datetime.utcnow() to return naive datetime
   - pytz.localize() requires naive datetime objects
   - Updated utcnow() wrapper to strip timezone info for compatibility

2. Add datetime.utcfromtimestamp() wrapper
   - Deprecated in Python 3.12
   - Created utcfromtimestamp() in salt.utils.timeutil
   - Replaced all usages in salt/modules/: status, rpm_lowpkg, dpkg_lowpkg, aptpkg
   - Fixed test file: tests/pytests/unit/states/file/test_tidied.py
   - Removed unused datetime imports

3. Fix boto3 test collection error on Windows
   - tests/unit/utils/test_boto3mod.py was accessing boto3.__version__
     even when boto3 not installed, causing NameError
   - Added HAS_BOTO3 check to version comparison skipif decorator
Use grains.get() with default value of 0 instead of direct dictionary
access to handle cases where osmajorrelease grain is not available.

Fixes test_owner and test_which failures on systems without osmajorrelease.
Ensure VirtualEnv test helper uses pip>=23.2 on Python 3.12+ to avoid
distutils import errors. Old pip versions try to import the distutils
module which was removed in Python 3.12.

This fixes test_pip_installed_pkgs_test_mode and other virtualenv-based
tests that were failing with "ModuleNotFoundError: No module named 'distutils'".
The legacy git code path in winrepo.update_git_repos() was storing
result["result"] (a boolean) instead of the target directory path.
This caused tests to fail with AttributeError when calling .endswith()
on the boolean value.

Changed to store gittarget (the actual path) to match the behavior
of the non-legacy gitfs code and what tests expect.

Fixes test_update_git_repos when GitPython/Pygit2 are not installed.
The backports module is not available in Python 3.12+ environments,
even though sys.version_info < (3, 13) evaluates to True. The test
was expecting backports to be included in thin.get_tops() output
based solely on Python version, but salt/utils/thin.py correctly
sets backports=None when the import fails.

Fixed by checking salt.utils.thin.backports is not None instead
of sys.version_info < (3, 13), matching the pattern used for
has_immutables.

Fixes test_get_tops, test_get_tops_extra_mods, and test_get_tops_so_mods
on Python 3.12.
- Update noxfile and requirements for Python 3.12 (multidict, timelib)
- Patch TestAccount and sshd_server to skip when privileges/binaries are missing
- Fix masterapi KeyError in minion_publish
- Resolve test environment crashes in test_config.py and test_saltcheck.py
- Align multidict version across all static requirement files
- Update test_pip.py to handle dynamic extras paths (extras-3.12)
- Fix path-list handling in extras_pypath fixture
- Add skipif for setproctitle check when C-extension is non-functional
- Replace os.getuid() with a cross-platform check using salt.utils.platform.is_windows()
- Ensure extras-3.12 path is correctly constructed on all platforms
…tests

- tests/pytests/functional/states/test_pip_state.py: Use Python 3.12 compatible pip versions and direct download links.
- salt/utils/thin.py: Use consistent package names in saltext discovery to fix blocklist logic.
- tests/conftest.py: Ensure SSH fixtures skip early when sshd is missing and synchronized scopes.
- tests/unit/modules/test_saltcheck.py: Improve mock flexibility for dynamic command calls.
- tests/pytests/unit/beacons/test_log_beacon.py: Safe TRACE log level access.
- tests/pytests/unit/client/ssh/test_single.py: Capture log messages regardless of global level.
- requirements/static: Regenerated and aligned multidict==6.1.0 repository-wide.
Comment on lines +189 to 191
not_after = salt.utils.timeutil.utcnow()
# And set the UTC timezone to the naive datetime resulting from parsing
not_after = not_after.replace(tzinfo=M2Crypto.ASN1.UTC)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these 2 lines should be update to be just

not_after = datetime.now(tzinfo=M2Crypto.ASN1.UTC)

and appears to be the case in other 2 places in this file.

Comment on lines +120 to +121
.not_valid_before(salt.utils.timeutil.utcnow())
.not_valid_after(salt.utils.timeutil.utcnow() + datetime.timedelta(days=365))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The utcnow calls in this file look like they have no need to use a zoneless datetime and can be replaced with datetime.now(timezone.utc)

Comment on lines +56 to 57
now = salt.utils.timeutil.utcnow()
future = now + datetime.timedelta(days=365)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks safe to use datetime.now(timezone.utc)

secret_access_key = "mock_SecretAccessKey"
token = "mock_Token"
expiration = (datetime.utcnow() + timedelta(seconds=900)).strftime(
expiration = (salt.utils.timeutil.utcnow() + timedelta(seconds=900)).strftime(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the changed usages in this class look safe to use datetime.now(timezone.utc).

Comment thread salt/utils/aws.py
Comment on lines +124 to 126
if not __Expiration__ or __Expiration__ < salt.utils.timeutil.utcnow().strftime(
"%Y-%m-%dT%H:%M:%SZ"
):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All utcnow changes in this file look like they can safely use datetime.now(timezone.utc)

Comment thread salt/spm/pkgdb/sqlite3.py
formula_def["version"],
formula_def["release"],
datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT"),
salt.utils.timeutil.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can safely use datetime.now(timezone.utc)

Comment thread salt/utils/timeutil.py
Comment on lines +71 to 72
time_now = utcnow()
time_at = time_now + dt
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can safely use datetime.now(timezone.utc)

dwoz added 3 commits April 13, 2026 10:47
- Handle RuntimeError with 'Event loop is closed' in AsyncReqMessageClient, RequestServer, and PublishServer coroutines in salt/transport/zeromq.py.
- Handle similar RuntimeError in LoadBalancerWorker in salt/transport/tcp.py.
- Filter dateutil's deprecated datetime calls in salt/__init__.py to prevent ResourceWarning/DeprecationWarning from breaking tests in Python 3.12.
- Introduce get_event_loop() and get_ioloop() helpers in salt/utils/asynchronous.py to safely retrieve or create event loops without triggering DeprecationWarnings.
- Replace direct tornado.ioloop.IOLoop.current() calls with salt.utils.asynchronous.get_ioloop() in salt/crypt.py, salt/channel/client.py, salt/minion.py, salt/utils/event.py, salt/transport/ws.py, salt/transport/tcp.py, and salt/transport/zeromq.py.
- Remove unused tornado imports in salt/channel/client.py.
- salt/states/ssh_auth.py: Improve sshre regex to handle optional comments correctly.
- salt/states/ssh_auth.py: Refactor manage state to properly parse each key in ssh_keys using the improved regex, ensuring correct identification for removal and presence checks.
- tests/pytests/functional/states/test_ssh_auth.py: Update test to use a valid RSA key and get_ioloop() helper for Python 3.12 compatibility.
dwoz and others added 17 commits April 13, 2026 21:25
- salt/utils/asynchronous.py: Simplify get_event_loop() and get_ioloop() to completely avoid DeprecationWarnings in Python 3.12 by directly creating/setting loops if none are running.
- salt/utils/asynchronous.py: Ensure SyncWrapper._target uses the instance s io_loop to avoid hangs due to mismatched loops in background threads.
- tests/pytests/functional/transport/zeromq/test_request_client.py: Fix spelling of Receive in test assertions to match code.
- salt/transport/ipc.py: Use salt.utils.asynchronous.get_ioloop() instead of direct tornado calls. Wrap IOStream creation in current_ioloop context to ensure correct binding.
- salt/utils/event.py: Bump deprecation version for EventPublisher to 3009.
- salt/utils/asynchronous.py: Further refine get_event_loop and get_ioloop to suppress DeprecationWarnings and ensure SyncWrapper uses the correct loop.
- tests/unit/transport/test_ipc.py: Fix thread synchronization by calling self.stop() via add_callback.
- salt/minion.py, salt/master.py: Use salt.utils.asynchronous.get_event_loop() helper for loop initialization.
- salt/transport/ws.py: Pass ssl=False instead of None to avoid DeprecationWarning.
- salt/utils/event.py: Make EventPublisher.close() loop-safe.
- salt/states/ssh_auth.py: Improve SSH key parsing regex and refactor manage() state.
- tests/pytests/unit/states/test_winrepo.py: Mock os.path.exists to fix Windows failure.
- tests/pytests/unit/utils/win_lgpo/test_netsh.py: Fix finally blocks to handle NotConfigured local state.
- tests/pytests/integration/cli/test_salt_key.py: Relax assertions to handle extra keys in test environment.
- salt/utils/nacl.py: Implement NACL_LOCK to synchronize C-extension calls.
- salt/utils/nacl.py: Detect and disable nacl module when zmq is present in Python 3.12+ to avoid hard crashes.
- salt/utils/nacl.py: Alias imports to satisfy pylint and prevent naming conflicts.
- tests/pytests/unit/modules/test_nacl.py: Gracefully skip tests when module is incompatible with environment.
- salt/utils/optsdict.py: Use memo dict in __deepcopy__ to prevent recursion.
- salt/utils/optsdict.py: Refactor DictProxy and ListProxy __init__ to avoid triggering COW during initialization.
- salt/transport/ipc.py: Handle closed event loops and stream closures in server/client coroutines.
- salt/transport/tcp.py: Handle closed event loops in PublishClient and PubServer. Ensure callbacks are correctly awaited.
- The transport-layer hangs in Python 3.12 were resolved by hardening salt/transport/tcp.py, which is the active underlying transport for local event publication.
- Verified that IPC transport is no longer used in core Salt and remains only for legacy test coverage.
- tests/pytests/unit/utils/test_nacl.py: Skip functional tests when nacl is incompatible with zmq on Python 3.12.
- salt/modules/junos.py: Ensure Junos names are defined even when jnpr.junos is missing. Added dummy _Mock class for unit testing stability.
- tests/pytests/unit/modules/test_junos.py: Skip tests when junos module cannot be loaded.
- salt/transport/zeromq.py: Add short delays after exceptions in request_handler to prevent tight retry loops and timeouts.
- tests/pytests/functional/master/test_event_publisher.py: Increase memory threshold to 200MB for Python 3.12 overhead.
- tests/pytests/functional/cli/test_salt_deltaproxy.py: Increase test timeout to 640s for CI stability.
…ibility

- Ensure asyncio tasks are properly cancelled and awaited in TCP and ZeroMQ transports.
- Prevent RuntimeError when closing event loops in SyncWrapper and AsyncTCPReqChannel.
- Add error checking to Windows process creation to detect silent failures.
- Increase memory threshold in test_event_publisher for Python 3.12 variance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants