From 17fe1e2dc7f867d4fdb31b19ea652fb26ea23626 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 31 May 2023 14:30:40 +1000 Subject: [PATCH] Bump dependencies and Python minimum (#175) Bump the Python minimum to Python 3.8 as Python 3.7 is nearing end of life and 3.8 as a baseline removes a few compatibility hacks. Also bumps some of the dev dependencies to the latest versions available. --- .github/workflows/ci.yml | 14 ++--- .pre-commit-config.yaml | 6 +- CHANGELOG.md | 5 +- README.md | 2 +- build_helpers/lib.sh | 11 ++-- pyproject.toml | 66 ++++++++++++++++++++-- requirements-dev.txt | 8 +-- setup.cfg | 61 -------------------- src/psrp/_async.py | 5 +- src/psrp/_compat.py | 31 ---------- src/psrp/_connection/named_pipe.py | 14 +---- src/psrp/_connection/process.py | 9 --- src/psrp/_connection/wsman.py | 17 ++---- src/psrp/_io/wsman.py | 16 +++--- src/psrp/_sync.py | 3 +- tests/tests_psrp/conftest.py | 17 ------ tests/tests_psrp/test_io_wsman.py | 5 +- tests/tests_psrp/test_wsman_integration.py | 19 +++---- 18 files changed, 106 insertions(+), 203 deletions(-) delete mode 100644 setup.cfg delete mode 100644 src/psrp/_compat.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76073fed..af0fc747 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,6 @@ on: types: - published - schedule: - - cron: 0 9 * * * - jobs: build: name: build library @@ -54,7 +51,6 @@ jobs: - ubuntu-latest - windows-latest python-version: - - 3.7 - 3.8 - 3.9 - '3.10' @@ -66,8 +62,6 @@ jobs: exclude: - os: ubuntu-latest python-arch: x86 - - os: windows-latest # lxml has no wheel for win-cp311 - python-version: '3.11' steps: - uses: actions/checkout@v3 @@ -141,8 +135,11 @@ jobs: name: publish needs: - test - runs-on: ubuntu-latest + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + steps: - uses: actions/download-artifact@v3 with: @@ -152,6 +149,3 @@ jobs: - name: Publish if: startsWith(github.ref, 'refs/tags/v') uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ece9728a..225d6b07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,16 @@ repos: - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.11.5 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.1 + rev: v1.3.0 hooks: - id: mypy additional_dependencies: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c01009e..8f5f5e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## 1.0.0b2 - TBD +* Bump minimum Python version to 3.8 +* Use setuptools with `pyproject.toml` as the project definition * Ensure the outgoing send buffer won't get too small to fit extra fragments -* Fix up certificate authentication over TLS 1.3 connections - Python 3.8 only -* Bump minimum Python version to 3.7 +* Fix up certificate authentication over TLS 1.3 connections * Cache Kerberos credentials to speed up re-authentication when starting a new WSMan connection on another thread * Fix deadlock when receiving certain WSManFault errors outside of a close operation * Fix invalid selector error when connecting to Exchange Online by re-using proper cookies diff --git a/README.md b/README.md index e0f28bb1..5b64304d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ To support Kerberos the `kerberos` extras package must be installed. See `How to Install` for more details -* CPython 3.7+ +* CPython 3.8+ * [cryptography](https://github.com/pyca/cryptography) * [psrpcore](https://github.com/jborean93/psrpcore) * [pyspnego](https://github.com/jborean93/pyspnego) diff --git a/build_helpers/lib.sh b/build_helpers/lib.sh index 02701302..974fd567 100755 --- a/build_helpers/lib.sh +++ b/build_helpers/lib.sh @@ -91,13 +91,14 @@ lib::setup::python_requirements() { DIST_LINK_PATH="${PWD}/dist" fi - python -m pip install pypsrp \ - --no-index \ + + # Getting the version is important so that pip prioritises our local dist + python -m pip install build + PSRP_VERSION="$( python -c "import build.util; print(build.util.project_wheel_metadata('.').get('Version'))" )" + + python -m pip install pypsrp[credssp,kerberos,named_pipe,ssh]=="${PSRP_VERSION}" \ --find-links "file://${DIST_LINK_PATH}" \ - --no-build-isolation \ - --no-dependencies \ --verbose - python -m pip install pypsrp[credssp,kerberos,named_pipe,socks,ssh] echo "Installing dev dependencies" python -m pip install -r requirements-dev.txt diff --git a/pyproject.toml b/pyproject.toml index 08ccaedd..40e93457 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,67 @@ [build-system] -requires = ["setuptools>=42.0.0"] +requires = [ + "setuptools >= 61.0.0", # Support for setuptools config in pyproject.toml +] build-backend = "setuptools.build_meta" +[project] +name = "pypsrp" +version = "1.0.0b2" +description = "PowerShell Remoting Protocol and WinRM for Python" +readme = "README.md" +requires-python = ">=3.8" +license = {file = "LICENSE"} +authors = [ + { name = "Jordan Borean", email = "jborean93@gmail.com" } +] +keywords = ["winrm", "psrp", "winrs", "windows", "powershell"] +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11" +] +dependencies = [ + "cryptography", + "httpcore < 1.0.0", + "httpx < 1.0.0", + "psrpcore >= 0.1.2", + "pyspnego >= 0.7.0, < 1.0.0", + "requests >= 2.9.1" # Old pypsrp namespace +] + +[project.urls] +homepage = "https://github.com/jborean93/pypsrp" + +[project.optional-dependencies] +credssp = [ + "requests-credssp >= 2.0.0" +] +kerberos = [ + "pyspnego[kerberos]" +] +named_pipe = [ + "psutil" +] +ssh = [ + "asyncssh" +] + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +psrp = ["py.typed"] +"psrp._pwsh" = ["*.ps1"] +pypsrp = ["py.typed"] +"pypsrp.pwsh_scripts" = ["*.ps1"] + [tool.black] line-length = 120 include = '\.pyi?$' @@ -43,10 +103,6 @@ check_untyped_defs = true warn_return_any = true warn_unreachable = true -[[tool.mypy.overrides]] -module = "psrp._compat" -warn_unused_ignores = false - [[tool.mypy.overrides]] module = "psrp._connection.named_pipe" warn_unused_ignores = false diff --git a/requirements-dev.txt b/requirements-dev.txt index dc384a52..f07ab149 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,11 +1,11 @@ asyncssh -black == 23.1.0 +black == 23.3.0 build gssapi ; sys_platform != 'win32' krb5 ; sys_platform != 'win32' httpx[socks] -isort == 5.11.5 -mypy == 1.0.1 +isort == 5.12.0 +mypy == 1.3.0 pre-commit psutil pytest @@ -16,9 +16,7 @@ PyYAML requests-credssp tox types-cryptography -types-dataclasses ; python_version < "3.7" types-requests types-psutil types-PyYAML -typing_extensions ; python_version < "3.8" xmldiff \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index cff9f400..00000000 --- a/setup.cfg +++ /dev/null @@ -1,61 +0,0 @@ -[metadata] -name = pypsrp -version = 1.0.0b2 -url = https://github.com/jborean93/pypsrp -author = Jordan Borean -author_email = jborean93@gmail.com -license = MIT -license_files = LICENSE -description = PowerShell Remoting Protocol and WinRM for Python -long_description = file: README.md -long_description_content_type = text/markdown -keywords = - winrm - psrp - winrs - windows - powershell -classifiers = - Development Status :: 4 - Beta - License :: OSI Approved :: MIT License - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - -[options] -zip_safe = True -package_dir = - =src -packages = find: -include_package_data = True -python_requires = >= 3.7 -install_requires = - cryptography - httpcore < 1.0.0 - httpx < 1.0.0 - psrpcore >= 0.1.2 - pyspnego >= 0.7.0, < 1.0.0 - requests >= 2.9.1 - typing_extensions; python_version<"3.8" - -[options.packages.find] -where = src - -[options.package_data] -psrp = py.typed -psrp._pwsh = *.ps1 -pypsrp = py.typed -pypsrp.pwsh_scripts = *.ps1 - -[options.extras_require] -credssp = - requests-credssp>=2.0.0 -kerberos = - pyspnego[kerberos] -named_pipe = - psutil -ssh = - asyncssh diff --git a/src/psrp/_async.py b/src/psrp/_async.py index d6644af6..fd5dc62a 100644 --- a/src/psrp/_async.py +++ b/src/psrp/_async.py @@ -53,7 +53,6 @@ WarningRecord, ) -from ._compat import SupportsIndex, iscoroutinefunction from ._connection.connection import AsyncConnection, ConnectionInfo from ._exceptions import ( PipelineFailed, @@ -156,7 +155,7 @@ async def complete(self) -> None: def insert( self, - index: SupportsIndex, + index: t.SupportsIndex, value: T, ) -> None: if self.completed: @@ -848,7 +847,7 @@ async def _invoke_host_call( error_record = None try: - if iscoroutinefunction(func): + if asyncio.iscoroutinefunction(func): return_value = await func() else: return_value = func() diff --git a/src/psrp/_compat.py b/src/psrp/_compat.py deleted file mode 100644 index 2b5bbcd7..00000000 --- a/src/psrp/_compat.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2022, Jordan Borean (@jborean93) -# MIT License (see LICENSE or https://opensource.org/licenses/MIT) - -import functools -import inspect -import typing as t - -# FUTURE: Remove once Python 3.8 is the minimum -try: - from typing_extensions import Literal, SupportsIndex # type: ignore -except ImportError: # pragma: no cover - from typing import Literal, SupportsIndex # type: ignore - - -def iscoroutinefunction( - value: t.Any, -) -> bool: - """Checks if a function is a coroutine even when wrapped by functools.""" - # FUTURE: Remove once Python 3.8 is the minimum - while isinstance(value, functools.partial): - value = value.func - - return inspect.iscoroutinefunction(value) - - -__all__ = [ - "Literal", - "SupportsIndex", - "iscoroutinefunction", -] diff --git a/src/psrp/_connection/named_pipe.py b/src/psrp/_connection/named_pipe.py index 7b01ea40..8cffd380 100644 --- a/src/psrp/_connection/named_pipe.py +++ b/src/psrp/_connection/named_pipe.py @@ -92,15 +92,6 @@ class NamedPipeInfo(ConnectionInfo): an integer for the name it will be configured to connect to the PowerShell host pipe of that process rather than an explicit pipe/socket name. - When using Python 3.6 or 3.7 on Windows the ``ProactorEventLoop`` event - loop must be used to create an asyncio pipe connection. This can be done by - setting. - - Example: - To set the ``ProactorEventLoop`` event loop on Windows do:: - - asycnio.set_event_loop_policy(asyncio.ProactorEventLoop()) - Args: name: The pipe name or PowerShell process id to connect to. """ @@ -188,7 +179,4 @@ async def write( async def stop(self) -> None: log.debug("Stopping Named Pipe connection") self._writer.close() - # FUTURE: Call directly once 3.7 is the minimum. - wait_closed = getattr(self._writer, "wait_closed", None) - if wait_closed: # pragma: no cover - await wait_closed() + await self._writer.wait_closed() diff --git a/src/psrp/_connection/process.py b/src/psrp/_connection/process.py index 91b48a34..7ba7b4a5 100644 --- a/src/psrp/_connection/process.py +++ b/src/psrp/_connection/process.py @@ -36,15 +36,6 @@ class ProcessInfo(ConnectionInfo): `-Version` entry is cruical to not running in version 2.0 compatibility mode. - When using Python 3.6 or 3.7 on Windows the ``ProactorEventLoop`` event - loop must be used to create an asyncio subprocess. This can be done by - settings. - - Example: - To set the ``ProactorEventLoop`` event loop on Windows do:: - - asycnio.set_event_loop_policy(asyncio.ProactorEventLoop()) - Args: executable: The executable to run, defaults to `pwsh`. arguments: A list of arguments to run, when the executable is `pwsh` diff --git a/src/psrp/_connection/wsman.py b/src/psrp/_connection/wsman.py index e474019b..a7e405d7 100644 --- a/src/psrp/_connection/wsman.py +++ b/src/psrp/_connection/wsman.py @@ -25,7 +25,6 @@ RunspacePoolStateMsg, ) -from psrp._compat import Literal from psrp._connection.connection import ( AsyncConnection, AsyncEventCallable, @@ -194,14 +193,6 @@ class WSManInfo(ConnectionInfo): create the initial SSLContext using that method and make the required changes on that instead of starting from scratch. - Asyncio on Windows can have problems with closing the vent loop. Using the - ``ProactorEventLoop`` event loop may solve some of those issues. - - Example: - To set the ``ProactorEventLoop`` event loop on Windows do:: - - asycnio.set_event_loop_policy(asyncio.ProactorEventLoop()) - Args: server: The server/hostname/IP/URI to use for connecting to the target. The full URI can be passed in here or just the hostname portion. If @@ -296,16 +287,16 @@ def __init__( self, server: str, *, - scheme: t.Optional[Literal["http", "https"]] = None, + scheme: t.Optional[t.Literal["http", "https"]] = None, port: int = -1, # Depends on the scheme (5985 if http else 5096) path: str = "wsman", - encryption: Literal["always", "auto", "never"] = "auto", + encryption: t.Literal["always", "auto", "never"] = "auto", ssl_context: t.Optional[ssl.SSLContext] = None, verify: t.Union[str, bool] = True, connection_timeout: float = 30.0, read_timeout: float = 30.0, # Authentication - auth: Literal["basic", "certificate", "credssp", "kerberos", "negotiate", "ntlm"] = "negotiate", + auth: t.Literal["basic", "certificate", "credssp", "kerberos", "negotiate", "ntlm"] = "negotiate", username: t.Optional[str] = None, password: t.Optional[str] = None, # Cert auth @@ -319,7 +310,7 @@ def __init__( negotiate_send_cbt: bool = True, # CredSSP credssp_ssl_context: t.Optional[ssl.SSLContext] = None, - credssp_auth_mechanism: Literal["kerberos", "negotiate", "ntlm"] = "negotiate", + credssp_auth_mechanism: t.Literal["kerberos", "negotiate", "ntlm"] = "negotiate", credssp_minimum_version: t.Optional[int] = None, # PSRP/WinRM Protocol max_envelope_size: t.Optional[int] = None, diff --git a/src/psrp/_io/wsman.py b/src/psrp/_io/wsman.py index 9e80b78a..e0e202e4 100644 --- a/src/psrp/_io/wsman.py +++ b/src/psrp/_io/wsman.py @@ -24,7 +24,6 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes -from psrp._compat import Literal from psrp._exceptions import WSManHTTPError log = logging.getLogger(__name__) @@ -175,10 +174,10 @@ class WSManConnectionData: tls: ssl.SSLContext = dataclasses.field(init=False) server: dataclasses.InitVar[str] - scheme: dataclasses.InitVar[t.Optional[Literal["http", "https"]]] = None + scheme: dataclasses.InitVar[t.Optional[t.Literal["http", "https"]]] = None port: dataclasses.InitVar[int] = -1 # Default is 5985 with http and 5986 with https path: dataclasses.InitVar[str] = "wsman" - encryption: Literal["always", "auto", "never"] = "auto" + encryption: t.Literal["always", "auto", "never"] = "auto" ssl_context: t.Optional[ssl.SSLContext] = None verify: dataclasses.InitVar[t.Union[str, bool]] = True @@ -186,7 +185,7 @@ class WSManConnectionData: read_timeout: float = 30.0 # Authentication - auth: Literal["basic", "certificate", "credssp", "kerberos", "negotiate", "ntlm"] = "negotiate" + auth: t.Literal["basic", "certificate", "credssp", "kerberos", "negotiate", "ntlm"] = "negotiate" username: t.Optional[str] = None password: t.Optional[str] = dataclasses.field(repr=False, default=None) # Cert auth @@ -200,7 +199,7 @@ class WSManConnectionData: negotiate_send_cbt: bool = True # CredSSP credssp_ssl_context: t.Optional[ssl.SSLContext] = None - credssp_auth_mechanism: Literal["kerberos", "negotiate", "ntlm"] = "negotiate" + credssp_auth_mechanism: t.Literal["kerberos", "negotiate", "ntlm"] = "negotiate" credssp_minimum_version: t.Optional[int] = None # FUTURE: reconnection settings @@ -255,10 +254,9 @@ def __post_init__( password=self.certificate_key_password, ) - # Needed for cert auth on TLS 1.3, unfortunately is a Python 3.8 - # only feature. - if hasattr(self.tls, "post_handshake_auth"): - self.tls.post_handshake_auth = True + # Needed for cert auth on TLS 1.3 as WSMan requests the cert after + # the TLS handshake. + self.tls.post_handshake_auth = True encryption = self.encryption.lower() if encryption == "always": diff --git a/src/psrp/_sync.py b/src/psrp/_sync.py index e6f15db3..d7503ad8 100644 --- a/src/psrp/_sync.py +++ b/src/psrp/_sync.py @@ -53,7 +53,6 @@ WarningRecord, ) -from ._compat import SupportsIndex from ._connection.connection import ConnectionInfo, SyncConnection from ._exceptions import ( PipelineFailed, @@ -156,7 +155,7 @@ def complete(self) -> None: def insert( self, - index: SupportsIndex, + index: t.SupportsIndex, value: T, ) -> None: if self.completed: diff --git a/tests/tests_psrp/conftest.py b/tests/tests_psrp/conftest.py index 5460cebc..041a2ae6 100644 --- a/tests/tests_psrp/conftest.py +++ b/tests/tests_psrp/conftest.py @@ -125,20 +125,3 @@ def psrp_named_pipe() -> t.Iterator[psrp.ConnectionInfo]: finally: proc.terminate() - - -@pytest.fixture() -def event_loop(): - # Older Python versions on Windows use a event loop policy that doesn't - # support subprocesses. Will need to use the ProactorEventLoop to test out - # those scenarios on the older versions. - # https://github.com/pytest-dev/pytest-asyncio/issues/227 - if sys.platform == "win32" and sys.version_info < (3, 8): - loop = asyncio.ProactorEventLoop() - else: - loop = asyncio.get_event_loop_policy().get_event_loop() - - try: - yield loop - finally: - loop.close() diff --git a/tests/tests_psrp/test_io_wsman.py b/tests/tests_psrp/test_io_wsman.py index c5f2cf6c..17d62b38 100644 --- a/tests/tests_psrp/test_io_wsman.py +++ b/tests/tests_psrp/test_io_wsman.py @@ -18,7 +18,6 @@ load_pem_private_key, ) -from psrp._compat import Literal from psrp._io import wsman WrapWinRMResult = collections.namedtuple("WrapWinRMResult", ["header", "data", "padding_length"]) @@ -170,8 +169,8 @@ def test_connection_info_encryption_always_with_invalid_auth(): ], ) def test_connection_info_message_encryption( - scheme: Literal["http", "https"], - encryption: Literal["always", "auto", "never"], + scheme: t.Literal["http", "https"], + encryption: t.Literal["always", "auto", "never"], expected: bool, ) -> None: actual = wsman.WSManConnectionData("server", scheme=scheme, auth="ntlm", encryption=encryption) diff --git a/tests/tests_psrp/test_wsman_integration.py b/tests/tests_psrp/test_wsman_integration.py index 81495014..70bb71bb 100644 --- a/tests/tests_psrp/test_wsman_integration.py +++ b/tests/tests_psrp/test_wsman_integration.py @@ -13,7 +13,6 @@ ) import psrp -from psrp._compat import Literal def get_server() -> str: @@ -39,14 +38,12 @@ def get_certificate_credential() -> str: cred_path = os.environ.get("PYPSRP_CERT_PATH", None) if not cred_path: pytest.skip("WSMan certificate integration test requires PYPSRP_CERT_PATH to be defined") - elif sys.version_info[:2] < (3, 8): - pytest.skip("Cert auth over TLS 1.3 requires Python 3.8 or newer") return cred_path @pytest.mark.parametrize("scheme", ["http", "https"]) -def test_wsman_basic_sync(scheme: Literal["http", "https"]) -> None: +def test_wsman_basic_sync(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() if "\\" in username: username = username.split("\\")[1] @@ -71,7 +68,7 @@ def test_wsman_basic_sync(scheme: Literal["http", "https"]) -> None: @pytest.mark.asyncio @pytest.mark.parametrize("scheme", ["http", "https"]) -async def test_wsman_basic_async(scheme: Literal["http", "https"]) -> None: +async def test_wsman_basic_async(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() if "\\" in username: username = username.split("\\")[1] @@ -204,7 +201,7 @@ async def test_wsman_cert_async(input_type: str, tmpdir: pathlib.Path) -> None: @pytest.mark.parametrize("scheme", ["http", "https"]) -def test_wsman_ntlm_sync(scheme: Literal["http", "https"]) -> None: +def test_wsman_ntlm_sync(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() connection = psrp.WSManInfo( server=get_server(), @@ -226,7 +223,7 @@ def test_wsman_ntlm_sync(scheme: Literal["http", "https"]) -> None: @pytest.mark.asyncio @pytest.mark.parametrize("scheme", ["http", "https"]) -async def test_wsman_ntlm_async(scheme: Literal["http", "https"]) -> None: +async def test_wsman_ntlm_async(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() connection = psrp.WSManInfo( server=get_server(), @@ -247,7 +244,7 @@ async def test_wsman_ntlm_async(scheme: Literal["http", "https"]) -> None: @pytest.mark.parametrize("scheme", ["http", "https"]) -def test_wsman_credssp_sync(scheme: Literal["http", "https"]) -> None: +def test_wsman_credssp_sync(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() connection = psrp.WSManInfo( server=get_server(), @@ -268,7 +265,7 @@ def test_wsman_credssp_sync(scheme: Literal["http", "https"]) -> None: @pytest.mark.asyncio @pytest.mark.parametrize("scheme", ["http", "https"]) -async def test_wsman_credssp_async(scheme: Literal["http", "https"]) -> None: +async def test_wsman_credssp_async(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() connection = psrp.WSManInfo( server=get_server(), @@ -288,7 +285,7 @@ async def test_wsman_credssp_async(scheme: Literal["http", "https"]) -> None: @pytest.mark.parametrize("scheme", ["http", "https"]) -def test_wsman_credssp__with_subauth_and_tls_sync(scheme: Literal["http", "https"]) -> None: +def test_wsman_credssp__with_subauth_and_tls_sync(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() connection = psrp.WSManInfo( server=get_server(), @@ -312,7 +309,7 @@ def test_wsman_credssp__with_subauth_and_tls_sync(scheme: Literal["http", "https @pytest.mark.asyncio @pytest.mark.parametrize("scheme", ["http", "https"]) -async def test_wsman_credssp_with_subauth_and_tls_async(scheme: Literal["http", "https"]) -> None: +async def test_wsman_credssp_with_subauth_and_tls_async(scheme: t.Literal["http", "https"]) -> None: username, password = get_username_password() connection = psrp.WSManInfo( server=get_server(),