diff --git a/tests/venv2/bin/Activate.ps1 b/tests/venv2/bin/Activate.ps1 deleted file mode 100644 index b49d77b..0000000 --- a/tests/venv2/bin/Activate.ps1 +++ /dev/null @@ -1,247 +0,0 @@ -<# -.Synopsis -Activate a Python virtual environment for the current PowerShell session. - -.Description -Pushes the python executable for a virtual environment to the front of the -$Env:PATH environment variable and sets the prompt to signify that you are -in a Python virtual environment. Makes use of the command line switches as -well as the `pyvenv.cfg` file values present in the virtual environment. - -.Parameter VenvDir -Path to the directory that contains the virtual environment to activate. The -default value for this is the parent of the directory that the Activate.ps1 -script is located within. - -.Parameter Prompt -The prompt prefix to display when this virtual environment is activated. By -default, this prompt is the name of the virtual environment folder (VenvDir) -surrounded by parentheses and followed by a single space (ie. '(.venv) '). - -.Example -Activate.ps1 -Activates the Python virtual environment that contains the Activate.ps1 script. - -.Example -Activate.ps1 -Verbose -Activates the Python virtual environment that contains the Activate.ps1 script, -and shows extra information about the activation as it executes. - -.Example -Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv -Activates the Python virtual environment located in the specified location. - -.Example -Activate.ps1 -Prompt "MyPython" -Activates the Python virtual environment that contains the Activate.ps1 script, -and prefixes the current prompt with the specified string (surrounded in -parentheses) while the virtual environment is active. - -.Notes -On Windows, it may be required to enable this Activate.ps1 script by setting the -execution policy for the user. You can do this by issuing the following PowerShell -command: - -PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -For more information on Execution Policies: -https://go.microsoft.com/fwlink/?LinkID=135170 - -#> -Param( - [Parameter(Mandatory = $false)] - [String] - $VenvDir, - [Parameter(Mandatory = $false)] - [String] - $Prompt -) - -<# Function declarations --------------------------------------------------- #> - -<# -.Synopsis -Remove all shell session elements added by the Activate script, including the -addition of the virtual environment's Python executable from the beginning of -the PATH variable. - -.Parameter NonDestructive -If present, do not remove this function from the global namespace for the -session. - -#> -function global:deactivate ([switch]$NonDestructive) { - # Revert to original values - - # The prior prompt: - if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { - Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt - Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT - } - - # The prior PYTHONHOME: - if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { - Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME - Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME - } - - # The prior PATH: - if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { - Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH - Remove-Item -Path Env:_OLD_VIRTUAL_PATH - } - - # Just remove the VIRTUAL_ENV altogether: - if (Test-Path -Path Env:VIRTUAL_ENV) { - Remove-Item -Path env:VIRTUAL_ENV - } - - # Just remove VIRTUAL_ENV_PROMPT altogether. - if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { - Remove-Item -Path env:VIRTUAL_ENV_PROMPT - } - - # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: - if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { - Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force - } - - # Leave deactivate function in the global namespace if requested: - if (-not $NonDestructive) { - Remove-Item -Path function:deactivate - } -} - -<# -.Description -Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the -given folder, and returns them in a map. - -For each line in the pyvenv.cfg file, if that line can be parsed into exactly -two strings separated by `=` (with any amount of whitespace surrounding the =) -then it is considered a `key = value` line. The left hand string is the key, -the right hand is the value. - -If the value starts with a `'` or a `"` then the first and last character is -stripped from the value before being captured. - -.Parameter ConfigDir -Path to the directory that contains the `pyvenv.cfg` file. -#> -function Get-PyVenvConfig( - [String] - $ConfigDir -) { - Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" - - # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). - $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue - - # An empty map will be returned if no config file is found. - $pyvenvConfig = @{ } - - if ($pyvenvConfigPath) { - - Write-Verbose "File exists, parse `key = value` lines" - $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath - - $pyvenvConfigContent | ForEach-Object { - $keyval = $PSItem -split "\s*=\s*", 2 - if ($keyval[0] -and $keyval[1]) { - $val = $keyval[1] - - # Remove extraneous quotations around a string value. - if ("'""".Contains($val.Substring(0, 1))) { - $val = $val.Substring(1, $val.Length - 2) - } - - $pyvenvConfig[$keyval[0]] = $val - Write-Verbose "Adding Key: '$($keyval[0])'='$val'" - } - } - } - return $pyvenvConfig -} - - -<# Begin Activate script --------------------------------------------------- #> - -# Determine the containing directory of this script -$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition -$VenvExecDir = Get-Item -Path $VenvExecPath - -Write-Verbose "Activation script is located in path: '$VenvExecPath'" -Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" -Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" - -# Set values required in priority: CmdLine, ConfigFile, Default -# First, get the location of the virtual environment, it might not be -# VenvExecDir if specified on the command line. -if ($VenvDir) { - Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" -} -else { - Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." - $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") - Write-Verbose "VenvDir=$VenvDir" -} - -# Next, read the `pyvenv.cfg` file to determine any required value such -# as `prompt`. -$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir - -# Next, set the prompt from the command line, or the config file, or -# just use the name of the virtual environment folder. -if ($Prompt) { - Write-Verbose "Prompt specified as argument, using '$Prompt'" -} -else { - Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" - if ($pyvenvCfg -and $pyvenvCfg['prompt']) { - Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" - $Prompt = $pyvenvCfg['prompt']; - } - else { - Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" - Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" - $Prompt = Split-Path -Path $venvDir -Leaf - } -} - -Write-Verbose "Prompt = '$Prompt'" -Write-Verbose "VenvDir='$VenvDir'" - -# Deactivate any currently active virtual environment, but leave the -# deactivate function in place. -deactivate -nondestructive - -# Now set the environment variable VIRTUAL_ENV, used by many tools to determine -# that there is an activated venv. -$env:VIRTUAL_ENV = $VenvDir - -if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { - - Write-Verbose "Setting prompt to '$Prompt'" - - # Set the prompt to include the env name - # Make sure _OLD_VIRTUAL_PROMPT is global - function global:_OLD_VIRTUAL_PROMPT { "" } - Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT - New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt - - function global:prompt { - Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " - _OLD_VIRTUAL_PROMPT - } - $env:VIRTUAL_ENV_PROMPT = $Prompt -} - -# Clear PYTHONHOME -if (Test-Path -Path Env:PYTHONHOME) { - Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME - Remove-Item -Path Env:PYTHONHOME -} - -# Add the venv to the PATH -Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH -$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/tests/venv2/bin/activate b/tests/venv2/bin/activate deleted file mode 100644 index eb0c705..0000000 --- a/tests/venv2/bin/activate +++ /dev/null @@ -1,69 +0,0 @@ -# This file must be used with "source bin/activate" *from bash* -# you cannot run it directly - -deactivate () { - # reset old environment variables - if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then - PATH="${_OLD_VIRTUAL_PATH:-}" - export PATH - unset _OLD_VIRTUAL_PATH - fi - if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then - PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" - export PYTHONHOME - unset _OLD_VIRTUAL_PYTHONHOME - fi - - # This should detect bash and zsh, which have a hash command that must - # be called to get it to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected - if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null - fi - - if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then - PS1="${_OLD_VIRTUAL_PS1:-}" - export PS1 - unset _OLD_VIRTUAL_PS1 - fi - - unset VIRTUAL_ENV - unset VIRTUAL_ENV_PROMPT - if [ ! "${1:-}" = "nondestructive" ] ; then - # Self destruct! - unset -f deactivate - fi -} - -# unset irrelevant variables -deactivate nondestructive - -VIRTUAL_ENV=/home/aitzol/podman/prosody-podman/tests/venv -export VIRTUAL_ENV - -_OLD_VIRTUAL_PATH="$PATH" -PATH="$VIRTUAL_ENV/"bin":$PATH" -export PATH - -# unset PYTHONHOME if set -# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) -# could use `if (set -u; : $PYTHONHOME) ;` in bash -if [ -n "${PYTHONHOME:-}" ] ; then - _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" - unset PYTHONHOME -fi - -if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then - _OLD_VIRTUAL_PS1="${PS1:-}" - PS1='(venv) '"${PS1:-}" - export PS1 - VIRTUAL_ENV_PROMPT='(venv) ' - export VIRTUAL_ENV_PROMPT -fi - -# This should detect bash and zsh, which have a hash command that must -# be called to get it to forget past commands. Without forgetting -# past commands the $PATH changes we made may not be respected -if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null -fi diff --git a/tests/venv2/bin/activate.csh b/tests/venv2/bin/activate.csh deleted file mode 100644 index 4b5592d..0000000 --- a/tests/venv2/bin/activate.csh +++ /dev/null @@ -1,26 +0,0 @@ -# This file must be used with "source bin/activate.csh" *from csh*. -# You cannot run it directly. -# Created by Davide Di Blasi . -# Ported to Python 3.3 venv by Andrew Svetlov - -alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' - -# Unset irrelevant variables. -deactivate nondestructive - -setenv VIRTUAL_ENV /home/aitzol/podman/prosody-podman/tests/venv - -set _OLD_VIRTUAL_PATH="$PATH" -setenv PATH "$VIRTUAL_ENV/"bin":$PATH" - - -set _OLD_VIRTUAL_PROMPT="$prompt" - -if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then - set prompt = '(venv) '"$prompt" - setenv VIRTUAL_ENV_PROMPT '(venv) ' -endif - -alias pydoc python -m pydoc - -rehash diff --git a/tests/venv2/bin/activate.fish b/tests/venv2/bin/activate.fish deleted file mode 100644 index 83fa442..0000000 --- a/tests/venv2/bin/activate.fish +++ /dev/null @@ -1,69 +0,0 @@ -# This file must be used with "source /bin/activate.fish" *from fish* -# (https://fishshell.com/); you cannot run it directly. - -function deactivate -d "Exit virtual environment and return to normal shell environment" - # reset old environment variables - if test -n "$_OLD_VIRTUAL_PATH" - set -gx PATH $_OLD_VIRTUAL_PATH - set -e _OLD_VIRTUAL_PATH - end - if test -n "$_OLD_VIRTUAL_PYTHONHOME" - set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME - set -e _OLD_VIRTUAL_PYTHONHOME - end - - if test -n "$_OLD_FISH_PROMPT_OVERRIDE" - set -e _OLD_FISH_PROMPT_OVERRIDE - # prevents error when using nested fish instances (Issue #93858) - if functions -q _old_fish_prompt - functions -e fish_prompt - functions -c _old_fish_prompt fish_prompt - functions -e _old_fish_prompt - end - end - - set -e VIRTUAL_ENV - set -e VIRTUAL_ENV_PROMPT - if test "$argv[1]" != "nondestructive" - # Self-destruct! - functions -e deactivate - end -end - -# Unset irrelevant variables. -deactivate nondestructive - -set -gx VIRTUAL_ENV /home/aitzol/podman/prosody-podman/tests/venv - -set -gx _OLD_VIRTUAL_PATH $PATH -set -gx PATH "$VIRTUAL_ENV/"bin $PATH - -# Unset PYTHONHOME if set. -if set -q PYTHONHOME - set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME - set -e PYTHONHOME -end - -if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" - # fish uses a function instead of an env var to generate the prompt. - - # Save the current fish_prompt function as the function _old_fish_prompt. - functions -c fish_prompt _old_fish_prompt - - # With the original prompt function renamed, we can override with our own. - function fish_prompt - # Save the return status of the last command. - set -l old_status $status - - # Output the venv prompt; color taken from the blue of the Python logo. - printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal) - - # Restore the return status of the previous command. - echo "exit $old_status" | . - # Output the original/"old" prompt. - _old_fish_prompt - end - - set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" - set -gx VIRTUAL_ENV_PROMPT '(venv) ' -end diff --git a/tests/venv2/bin/cffi-gen-src b/tests/venv2/bin/cffi-gen-src deleted file mode 100755 index ef2de41..0000000 --- a/tests/venv2/bin/cffi-gen-src +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from cffi._cffi_gen_src import run -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(run()) diff --git a/tests/venv2/bin/pip b/tests/venv2/bin/pip deleted file mode 100755 index 65b9127..0000000 --- a/tests/venv2/bin/pip +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip._internal.cli.main import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/tests/venv2/bin/pip-chill b/tests/venv2/bin/pip-chill deleted file mode 100755 index d6b4bff..0000000 --- a/tests/venv2/bin/pip-chill +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip_chill.cli import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/tests/venv2/bin/pip3 b/tests/venv2/bin/pip3 deleted file mode 100755 index 65b9127..0000000 --- a/tests/venv2/bin/pip3 +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip._internal.cli.main import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/tests/venv2/bin/pip3.11 b/tests/venv2/bin/pip3.11 deleted file mode 100755 index 65b9127..0000000 --- a/tests/venv2/bin/pip3.11 +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip._internal.cli.main import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/tests/venv2/bin/py.test b/tests/venv2/bin/py.test deleted file mode 100755 index 7aa7fa9..0000000 --- a/tests/venv2/bin/py.test +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from _pytest.config import _console_main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(_console_main()) diff --git a/tests/venv2/bin/pybabel b/tests/venv2/bin/pybabel deleted file mode 100755 index 9c80811..0000000 --- a/tests/venv2/bin/pybabel +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from babel.messages.frontend import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/tests/venv2/bin/pygmentize b/tests/venv2/bin/pygmentize deleted file mode 100755 index 3551b84..0000000 --- a/tests/venv2/bin/pygmentize +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pygments.cmdline import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/tests/venv2/bin/pytest b/tests/venv2/bin/pytest deleted file mode 100755 index 7aa7fa9..0000000 --- a/tests/venv2/bin/pytest +++ /dev/null @@ -1,8 +0,0 @@ -#!/media/aitzol/GARAPENA/DOCKER/XMPP/prosody-docker-13/tests/venv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from _pytest.config import _console_main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(_console_main()) diff --git a/tests/venv2/bin/python b/tests/venv2/bin/python deleted file mode 120000 index b8a0adb..0000000 --- a/tests/venv2/bin/python +++ /dev/null @@ -1 +0,0 @@ -python3 \ No newline at end of file diff --git a/tests/venv2/bin/python3 b/tests/venv2/bin/python3 deleted file mode 120000 index ae65fda..0000000 --- a/tests/venv2/bin/python3 +++ /dev/null @@ -1 +0,0 @@ -/usr/bin/python3 \ No newline at end of file diff --git a/tests/venv2/bin/python3.11 b/tests/venv2/bin/python3.11 deleted file mode 120000 index b8a0adb..0000000 --- a/tests/venv2/bin/python3.11 +++ /dev/null @@ -1 +0,0 @@ -python3 \ No newline at end of file diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/SSL.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/SSL.py deleted file mode 100644 index b7d5887..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/SSL.py +++ /dev/null @@ -1,3361 +0,0 @@ -from __future__ import annotations - -import os -import socket -import sys -import typing -import warnings -from collections.abc import Sequence -from errno import errorcode -from functools import partial, wraps -from itertools import chain, count -from sys import platform -from typing import Any, Callable, Optional, TypeVar -from weakref import WeakValueDictionary - -if sys.version_info >= (3, 13): - from warnings import deprecated -else: - from typing_extensions import deprecated - -from cryptography import x509 -from cryptography.hazmat.primitives.asymmetric import ec - -from OpenSSL._util import ( - StrOrBytesPath as _StrOrBytesPath, -) -from OpenSSL._util import ( - exception_from_error_queue as _exception_from_error_queue, -) -from OpenSSL._util import ( - ffi as _ffi, -) -from OpenSSL._util import ( - lib as _lib, -) -from OpenSSL._util import ( - make_assert as _make_assert, -) -from OpenSSL._util import ( - no_zero_allocator as _no_zero_allocator, -) -from OpenSSL._util import ( - path_bytes as _path_bytes, -) -from OpenSSL._util import ( - text_to_bytes_and_warn as _text_to_bytes_and_warn, -) -from OpenSSL.crypto import ( - FILETYPE_PEM, - X509, - PKey, - X509Name, - X509Store, - _EllipticCurve, - _PassphraseHelper, - _PrivateKey, -) - -__all__ = [ - "DTLS_CLIENT_METHOD", - "DTLS_METHOD", - "DTLS_SERVER_METHOD", - "MODE_RELEASE_BUFFERS", - "NO_OVERLAPPING_PROTOCOLS", - "OPENSSL_BUILT_ON", - "OPENSSL_CFLAGS", - "OPENSSL_DIR", - "OPENSSL_PLATFORM", - "OPENSSL_VERSION", - "OPENSSL_VERSION_NUMBER", - "OP_ALL", - "OP_CIPHER_SERVER_PREFERENCE", - "OP_DONT_INSERT_EMPTY_FRAGMENTS", - "OP_EPHEMERAL_RSA", - "OP_MICROSOFT_BIG_SSLV3_BUFFER", - "OP_MICROSOFT_SESS_ID_BUG", - "OP_MSIE_SSLV2_RSA_PADDING", - "OP_NETSCAPE_CA_DN_BUG", - "OP_NETSCAPE_CHALLENGE_BUG", - "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", - "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", - "OP_NO_COMPRESSION", - "OP_NO_QUERY_MTU", - "OP_NO_TICKET", - "OP_PKCS1_CHECK_1", - "OP_PKCS1_CHECK_2", - "OP_SINGLE_DH_USE", - "OP_SINGLE_ECDH_USE", - "OP_SSLEAY_080_CLIENT_DH_BUG", - "OP_SSLREF2_REUSE_CERT_TYPE_BUG", - "OP_TLS_BLOCK_PADDING_BUG", - "OP_TLS_D5_BUG", - "OP_TLS_ROLLBACK_BUG", - "RECEIVED_SHUTDOWN", - "SENT_SHUTDOWN", - "SESS_CACHE_BOTH", - "SESS_CACHE_CLIENT", - "SESS_CACHE_NO_AUTO_CLEAR", - "SESS_CACHE_NO_INTERNAL", - "SESS_CACHE_NO_INTERNAL_LOOKUP", - "SESS_CACHE_NO_INTERNAL_STORE", - "SESS_CACHE_OFF", - "SESS_CACHE_SERVER", - "SSL3_VERSION", - "SSLEAY_BUILT_ON", - "SSLEAY_CFLAGS", - "SSLEAY_DIR", - "SSLEAY_PLATFORM", - "SSLEAY_VERSION", - "SSL_CB_ACCEPT_EXIT", - "SSL_CB_ACCEPT_LOOP", - "SSL_CB_ALERT", - "SSL_CB_CONNECT_EXIT", - "SSL_CB_CONNECT_LOOP", - "SSL_CB_EXIT", - "SSL_CB_HANDSHAKE_DONE", - "SSL_CB_HANDSHAKE_START", - "SSL_CB_LOOP", - "SSL_CB_READ", - "SSL_CB_READ_ALERT", - "SSL_CB_WRITE", - "SSL_CB_WRITE_ALERT", - "SSL_ST_ACCEPT", - "SSL_ST_CONNECT", - "SSL_ST_MASK", - "TLS1_1_VERSION", - "TLS1_2_VERSION", - "TLS1_3_VERSION", - "TLS1_VERSION", - "TLS_CLIENT_METHOD", - "TLS_METHOD", - "TLS_SERVER_METHOD", - "VERIFY_CLIENT_ONCE", - "VERIFY_FAIL_IF_NO_PEER_CERT", - "VERIFY_NONE", - "VERIFY_PEER", - "Connection", - "Context", - "Error", - "OP_NO_SSLv2", - "OP_NO_SSLv3", - "OP_NO_TLSv1", - "OP_NO_TLSv1_1", - "OP_NO_TLSv1_2", - "OP_NO_TLSv1_3", - "SSLeay_version", - "SSLv23_METHOD", - "Session", - "SysCallError", - "TLSv1_1_METHOD", - "TLSv1_2_METHOD", - "TLSv1_METHOD", - "WantReadError", - "WantWriteError", - "WantX509LookupError", - "X509VerificationCodes", - "ZeroReturnError", -] - - -OPENSSL_VERSION_NUMBER: int = _lib.OPENSSL_VERSION_NUMBER -OPENSSL_VERSION: int = _lib.OPENSSL_VERSION -OPENSSL_CFLAGS: int = _lib.OPENSSL_CFLAGS -OPENSSL_PLATFORM: int = _lib.OPENSSL_PLATFORM -OPENSSL_DIR: int = _lib.OPENSSL_DIR -OPENSSL_BUILT_ON: int = _lib.OPENSSL_BUILT_ON - -SSLEAY_VERSION = OPENSSL_VERSION -SSLEAY_CFLAGS = OPENSSL_CFLAGS -SSLEAY_PLATFORM = OPENSSL_PLATFORM -SSLEAY_DIR = OPENSSL_DIR -SSLEAY_BUILT_ON = OPENSSL_BUILT_ON - -SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN -RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN - -SSLv23_METHOD = 3 -TLSv1_METHOD = 4 -TLSv1_1_METHOD = 5 -TLSv1_2_METHOD = 6 -TLS_METHOD = 7 -TLS_SERVER_METHOD = 8 -TLS_CLIENT_METHOD = 9 -DTLS_METHOD = 10 -DTLS_SERVER_METHOD = 11 -DTLS_CLIENT_METHOD = 12 - -SSL3_VERSION: int = _lib.SSL3_VERSION -TLS1_VERSION: int = _lib.TLS1_VERSION -TLS1_1_VERSION: int = _lib.TLS1_1_VERSION -TLS1_2_VERSION: int = _lib.TLS1_2_VERSION -TLS1_3_VERSION: int = _lib.TLS1_3_VERSION - -OP_NO_SSLv2: int = _lib.SSL_OP_NO_SSLv2 -OP_NO_SSLv3: int = _lib.SSL_OP_NO_SSLv3 -OP_NO_TLSv1: int = _lib.SSL_OP_NO_TLSv1 -OP_NO_TLSv1_1: int = _lib.SSL_OP_NO_TLSv1_1 -OP_NO_TLSv1_2: int = _lib.SSL_OP_NO_TLSv1_2 -OP_NO_TLSv1_3: int = _lib.SSL_OP_NO_TLSv1_3 - -MODE_RELEASE_BUFFERS: int = _lib.SSL_MODE_RELEASE_BUFFERS - -OP_SINGLE_DH_USE: int = _lib.SSL_OP_SINGLE_DH_USE -OP_SINGLE_ECDH_USE: int = _lib.SSL_OP_SINGLE_ECDH_USE -OP_EPHEMERAL_RSA: int = _lib.SSL_OP_EPHEMERAL_RSA -OP_MICROSOFT_SESS_ID_BUG: int = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG -OP_NETSCAPE_CHALLENGE_BUG: int = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG -OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: int = ( - _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG -) -OP_SSLREF2_REUSE_CERT_TYPE_BUG: int = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG -OP_MICROSOFT_BIG_SSLV3_BUFFER: int = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER -OP_MSIE_SSLV2_RSA_PADDING: int = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING -OP_SSLEAY_080_CLIENT_DH_BUG: int = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG -OP_TLS_D5_BUG: int = _lib.SSL_OP_TLS_D5_BUG -OP_TLS_BLOCK_PADDING_BUG: int = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG -OP_DONT_INSERT_EMPTY_FRAGMENTS: int = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS -OP_CIPHER_SERVER_PREFERENCE: int = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE -OP_TLS_ROLLBACK_BUG: int = _lib.SSL_OP_TLS_ROLLBACK_BUG -OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 -OP_PKCS1_CHECK_2: int = _lib.SSL_OP_PKCS1_CHECK_2 -OP_NETSCAPE_CA_DN_BUG: int = _lib.SSL_OP_NETSCAPE_CA_DN_BUG -OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: int = ( - _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG -) -OP_NO_COMPRESSION: int = _lib.SSL_OP_NO_COMPRESSION - -OP_NO_QUERY_MTU: int = _lib.SSL_OP_NO_QUERY_MTU -try: - OP_COOKIE_EXCHANGE: int | None = _lib.SSL_OP_COOKIE_EXCHANGE - __all__.append("OP_COOKIE_EXCHANGE") -except AttributeError: - OP_COOKIE_EXCHANGE = None -OP_NO_TICKET: int = _lib.SSL_OP_NO_TICKET - -try: - OP_NO_RENEGOTIATION: int = _lib.SSL_OP_NO_RENEGOTIATION - __all__.append("OP_NO_RENEGOTIATION") -except AttributeError: - pass - -try: - OP_IGNORE_UNEXPECTED_EOF: int = _lib.SSL_OP_IGNORE_UNEXPECTED_EOF - __all__.append("OP_IGNORE_UNEXPECTED_EOF") -except AttributeError: - pass - -try: - OP_LEGACY_SERVER_CONNECT: int = _lib.SSL_OP_LEGACY_SERVER_CONNECT - __all__.append("OP_LEGACY_SERVER_CONNECT") -except AttributeError: - pass - -OP_ALL: int = _lib.SSL_OP_ALL - -VERIFY_PEER: int = _lib.SSL_VERIFY_PEER -VERIFY_FAIL_IF_NO_PEER_CERT: int = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT -VERIFY_CLIENT_ONCE: int = _lib.SSL_VERIFY_CLIENT_ONCE -VERIFY_NONE: int = _lib.SSL_VERIFY_NONE - -SESS_CACHE_OFF: int = _lib.SSL_SESS_CACHE_OFF -SESS_CACHE_CLIENT: int = _lib.SSL_SESS_CACHE_CLIENT -SESS_CACHE_SERVER: int = _lib.SSL_SESS_CACHE_SERVER -SESS_CACHE_BOTH: int = _lib.SSL_SESS_CACHE_BOTH -SESS_CACHE_NO_AUTO_CLEAR: int = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR -SESS_CACHE_NO_INTERNAL_LOOKUP: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP -SESS_CACHE_NO_INTERNAL_STORE: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE -SESS_CACHE_NO_INTERNAL: int = _lib.SSL_SESS_CACHE_NO_INTERNAL - -SSL_ST_CONNECT: int = _lib.SSL_ST_CONNECT -SSL_ST_ACCEPT: int = _lib.SSL_ST_ACCEPT -SSL_ST_MASK: int = _lib.SSL_ST_MASK - -SSL_CB_LOOP: int = _lib.SSL_CB_LOOP -SSL_CB_EXIT: int = _lib.SSL_CB_EXIT -SSL_CB_READ: int = _lib.SSL_CB_READ -SSL_CB_WRITE: int = _lib.SSL_CB_WRITE -SSL_CB_ALERT: int = _lib.SSL_CB_ALERT -SSL_CB_READ_ALERT: int = _lib.SSL_CB_READ_ALERT -SSL_CB_WRITE_ALERT: int = _lib.SSL_CB_WRITE_ALERT -SSL_CB_ACCEPT_LOOP: int = _lib.SSL_CB_ACCEPT_LOOP -SSL_CB_ACCEPT_EXIT: int = _lib.SSL_CB_ACCEPT_EXIT -SSL_CB_CONNECT_LOOP: int = _lib.SSL_CB_CONNECT_LOOP -SSL_CB_CONNECT_EXIT: int = _lib.SSL_CB_CONNECT_EXIT -SSL_CB_HANDSHAKE_START: int = _lib.SSL_CB_HANDSHAKE_START -SSL_CB_HANDSHAKE_DONE: int = _lib.SSL_CB_HANDSHAKE_DONE - -_Buffer = typing.Union[bytes, bytearray, memoryview] -_T = TypeVar("_T") - - -class _NoOverlappingProtocols: - pass - - -NO_OVERLAPPING_PROTOCOLS = _NoOverlappingProtocols() - -# Callback types. -_ALPNSelectCallback = Callable[ - [ - "Connection", - typing.List[bytes], - ], - typing.Union[bytes, _NoOverlappingProtocols], -] -_CookieGenerateCallback = Callable[["Connection"], bytes] -_CookieVerifyCallback = Callable[["Connection", bytes], bool] -_OCSPClientCallback = Callable[["Connection", bytes, Optional[_T]], bool] -_OCSPServerCallback = Callable[["Connection", Optional[_T]], bytes] -_PassphraseCallback = Callable[[int, bool, Optional[_T]], bytes] -_VerifyCallback = Callable[["Connection", X509, int, int, int], bool] - - -class X509VerificationCodes: - """ - Success and error codes for X509 verification, as returned by the - underlying ``X509_STORE_CTX_get_error()`` function and passed by pyOpenSSL - to verification callback functions. - - See `OpenSSL Verification Errors - `_ - for details. - """ - - OK = _lib.X509_V_OK - ERR_UNABLE_TO_GET_ISSUER_CERT = _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT - ERR_UNABLE_TO_GET_CRL = _lib.X509_V_ERR_UNABLE_TO_GET_CRL - ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = ( - _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE - ) - ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = ( - _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE - ) - ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = ( - _lib.X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY - ) - ERR_CERT_SIGNATURE_FAILURE = _lib.X509_V_ERR_CERT_SIGNATURE_FAILURE - ERR_CRL_SIGNATURE_FAILURE = _lib.X509_V_ERR_CRL_SIGNATURE_FAILURE - ERR_CERT_NOT_YET_VALID = _lib.X509_V_ERR_CERT_NOT_YET_VALID - ERR_CERT_HAS_EXPIRED = _lib.X509_V_ERR_CERT_HAS_EXPIRED - ERR_CRL_NOT_YET_VALID = _lib.X509_V_ERR_CRL_NOT_YET_VALID - ERR_CRL_HAS_EXPIRED = _lib.X509_V_ERR_CRL_HAS_EXPIRED - ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = ( - _lib.X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD - ) - ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = ( - _lib.X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD - ) - ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = ( - _lib.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD - ) - ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = ( - _lib.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD - ) - ERR_OUT_OF_MEM = _lib.X509_V_ERR_OUT_OF_MEM - ERR_DEPTH_ZERO_SELF_SIGNED_CERT = ( - _lib.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT - ) - ERR_SELF_SIGNED_CERT_IN_CHAIN = _lib.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN - ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = ( - _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY - ) - ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = ( - _lib.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE - ) - ERR_CERT_CHAIN_TOO_LONG = _lib.X509_V_ERR_CERT_CHAIN_TOO_LONG - ERR_CERT_REVOKED = _lib.X509_V_ERR_CERT_REVOKED - ERR_INVALID_CA = _lib.X509_V_ERR_INVALID_CA - ERR_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PATH_LENGTH_EXCEEDED - ERR_INVALID_PURPOSE = _lib.X509_V_ERR_INVALID_PURPOSE - ERR_CERT_UNTRUSTED = _lib.X509_V_ERR_CERT_UNTRUSTED - ERR_CERT_REJECTED = _lib.X509_V_ERR_CERT_REJECTED - ERR_SUBJECT_ISSUER_MISMATCH = _lib.X509_V_ERR_SUBJECT_ISSUER_MISMATCH - ERR_AKID_SKID_MISMATCH = _lib.X509_V_ERR_AKID_SKID_MISMATCH - ERR_AKID_ISSUER_SERIAL_MISMATCH = ( - _lib.X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH - ) - ERR_KEYUSAGE_NO_CERTSIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CERTSIGN - ERR_UNABLE_TO_GET_CRL_ISSUER = _lib.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER - ERR_UNHANDLED_CRITICAL_EXTENSION = ( - _lib.X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION - ) - ERR_KEYUSAGE_NO_CRL_SIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN - ERR_UNHANDLED_CRITICAL_CRL_EXTENSION = ( - _lib.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION - ) - ERR_INVALID_NON_CA = _lib.X509_V_ERR_INVALID_NON_CA - ERR_PROXY_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED - ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE = ( - _lib.X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE - ) - ERR_PROXY_CERTIFICATES_NOT_ALLOWED = ( - _lib.X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED - ) - ERR_INVALID_EXTENSION = _lib.X509_V_ERR_INVALID_EXTENSION - ERR_INVALID_POLICY_EXTENSION = _lib.X509_V_ERR_INVALID_POLICY_EXTENSION - ERR_NO_EXPLICIT_POLICY = _lib.X509_V_ERR_NO_EXPLICIT_POLICY - ERR_DIFFERENT_CRL_SCOPE = _lib.X509_V_ERR_DIFFERENT_CRL_SCOPE - ERR_UNSUPPORTED_EXTENSION_FEATURE = ( - _lib.X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE - ) - ERR_UNNESTED_RESOURCE = _lib.X509_V_ERR_UNNESTED_RESOURCE - ERR_PERMITTED_VIOLATION = _lib.X509_V_ERR_PERMITTED_VIOLATION - ERR_EXCLUDED_VIOLATION = _lib.X509_V_ERR_EXCLUDED_VIOLATION - ERR_SUBTREE_MINMAX = _lib.X509_V_ERR_SUBTREE_MINMAX - ERR_UNSUPPORTED_CONSTRAINT_TYPE = ( - _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE - ) - ERR_UNSUPPORTED_CONSTRAINT_SYNTAX = ( - _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX - ) - ERR_UNSUPPORTED_NAME_SYNTAX = _lib.X509_V_ERR_UNSUPPORTED_NAME_SYNTAX - ERR_CRL_PATH_VALIDATION_ERROR = _lib.X509_V_ERR_CRL_PATH_VALIDATION_ERROR - ERR_HOSTNAME_MISMATCH = _lib.X509_V_ERR_HOSTNAME_MISMATCH - ERR_EMAIL_MISMATCH = _lib.X509_V_ERR_EMAIL_MISMATCH - ERR_IP_ADDRESS_MISMATCH = _lib.X509_V_ERR_IP_ADDRESS_MISMATCH - ERR_APPLICATION_VERIFICATION = _lib.X509_V_ERR_APPLICATION_VERIFICATION - - -# Taken from https://golang.org/src/crypto/x509/root_linux.go -_CERTIFICATE_FILE_LOCATIONS = [ - "/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc. - "/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6 - "/etc/ssl/ca-bundle.pem", # OpenSUSE - "/etc/pki/tls/cacert.pem", # OpenELEC - "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7 -] - -_CERTIFICATE_PATH_LOCATIONS = [ - "/etc/ssl/certs", # SLES10/SLES11 -] - -# These values are compared to output from cffi's ffi.string so they must be -# byte strings. -_CRYPTOGRAPHY_MANYLINUX_CA_DIR = b"/opt/pyca/cryptography/openssl/certs" -_CRYPTOGRAPHY_MANYLINUX_CA_FILE = b"/opt/pyca/cryptography/openssl/cert.pem" - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.SSL` API. - """ - - -_raise_current_error = partial(_exception_from_error_queue, Error) -_openssl_assert = _make_assert(Error) - - -class WantReadError(Error): - pass - - -class WantWriteError(Error): - pass - - -class WantX509LookupError(Error): - pass - - -class ZeroReturnError(Error): - pass - - -class SysCallError(Error): - pass - - -class _CallbackExceptionHelper: - """ - A base class for wrapper classes that allow for intelligent exception - handling in OpenSSL callbacks. - - :ivar list _problems: Any exceptions that occurred while executing in a - context where they could not be raised in the normal way. Typically - this is because OpenSSL has called into some Python code and requires a - return value. The exceptions are saved to be raised later when it is - possible to do so. - """ - - def __init__(self) -> None: - self._problems: list[Exception] = [] - - def raise_if_problem(self) -> None: - """ - Raise an exception from the OpenSSL error queue or that was previously - captured whe running a callback. - """ - if self._problems: - try: - _raise_current_error() - except Error: - pass - raise self._problems.pop(0) - - -class _VerifyHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as a certificate verification - callback. - """ - - def __init__(self, callback: _VerifyCallback) -> None: - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ok, store_ctx): # type: ignore[no-untyped-def] - x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) - _lib.X509_up_ref(x509) - cert = X509._from_raw_x509_ptr(x509) - error_number = _lib.X509_STORE_CTX_get_error(store_ctx) - error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) - - index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx() - ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index) - connection = Connection._reverse_mapping[ssl] - - try: - result = callback( - connection, cert, error_number, error_depth, ok - ) - except Exception as e: - self._problems.append(e) - return 0 - else: - if result: - _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) - return 1 - else: - return 0 - - self.callback = _ffi.callback( - "int (*)(int, X509_STORE_CTX *)", wrapper - ) - - -class _ALPNSelectHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an ALPN selection callback. - """ - - def __init__(self, callback: _ALPNSelectCallback) -> None: - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, out, outlen, in_, inlen, arg): # type: ignore[no-untyped-def] - try: - conn = Connection._reverse_mapping[ssl] - - # The string passed to us is made up of multiple - # length-prefixed bytestrings. We need to split that into a - # list. - instr = _ffi.buffer(in_, inlen)[:] - protolist = [] - while instr: - encoded_len = instr[0] - proto = instr[1 : encoded_len + 1] - protolist.append(proto) - instr = instr[encoded_len + 1 :] - - # Call the callback - outbytes = callback(conn, protolist) - any_accepted = True - if outbytes is NO_OVERLAPPING_PROTOCOLS: - outbytes = b"" - any_accepted = False - elif not isinstance(outbytes, bytes): - raise TypeError( - "ALPN callback must return a bytestring or the " - "special NO_OVERLAPPING_PROTOCOLS sentinel value." - ) - - # Save our callback arguments on the connection object to make - # sure that they don't get freed before OpenSSL can use them. - # Then, return them in the appropriate output parameters. - conn._alpn_select_callback_args = [ - _ffi.new("unsigned char *", len(outbytes)), - _ffi.new("unsigned char[]", outbytes), - ] - outlen[0] = conn._alpn_select_callback_args[0][0] - out[0] = conn._alpn_select_callback_args[1] - if not any_accepted: - return _lib.SSL_TLSEXT_ERR_NOACK - return _lib.SSL_TLSEXT_ERR_OK - except Exception as e: - self._problems.append(e) - return _lib.SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback( - ( - "int (*)(SSL *, unsigned char **, unsigned char *, " - "const unsigned char *, unsigned int, void *)" - ), - wrapper, - ) - - -class _OCSPServerCallbackHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an OCSP callback for the server - side. - - Annoyingly, OpenSSL defines one OCSP callback but uses it in two different - ways. For servers, that callback is expected to retrieve some OCSP data and - hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, - SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback - is expected to check the OCSP data, and returns a negative value on error, - 0 if the response is not acceptable, or positive if it is. These are - mutually exclusive return code behaviours, and they mean that we need two - helpers so that we always return an appropriate error code if the user's - code throws an exception. - - Given that we have to have two helpers anyway, these helpers are a bit more - helpery than most: specifically, they hide a few more of the OpenSSL - functions so that the user has an easier time writing these callbacks. - - This helper implements the server side. - """ - - def __init__(self, callback: _OCSPServerCallback[Any]) -> None: - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, cdata): # type: ignore[no-untyped-def] - try: - conn = Connection._reverse_mapping[ssl] - - # Extract the data if any was provided. - if cdata != _ffi.NULL: - data = _ffi.from_handle(cdata) - else: - data = None - - # Call the callback. - ocsp_data = callback(conn, data) - - if not isinstance(ocsp_data, bytes): - raise TypeError("OCSP callback must return a bytestring.") - - # If the OCSP data was provided, we will pass it to OpenSSL. - # However, we have an early exit here: if no OCSP data was - # provided we will just exit out and tell OpenSSL that there - # is nothing to do. - if not ocsp_data: - return 3 # SSL_TLSEXT_ERR_NOACK - - # OpenSSL takes ownership of this data and expects it to have - # been allocated by OPENSSL_malloc. - ocsp_data_length = len(ocsp_data) - data_ptr = _lib.OPENSSL_malloc(ocsp_data_length) - _ffi.buffer(data_ptr, ocsp_data_length)[:] = ocsp_data - - _lib.SSL_set_tlsext_status_ocsp_resp( - ssl, data_ptr, ocsp_data_length - ) - - return 0 - except Exception as e: - self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) - - -class _OCSPClientCallbackHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an OCSP callback for the client - side. - - Annoyingly, OpenSSL defines one OCSP callback but uses it in two different - ways. For servers, that callback is expected to retrieve some OCSP data and - hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, - SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback - is expected to check the OCSP data, and returns a negative value on error, - 0 if the response is not acceptable, or positive if it is. These are - mutually exclusive return code behaviours, and they mean that we need two - helpers so that we always return an appropriate error code if the user's - code throws an exception. - - Given that we have to have two helpers anyway, these helpers are a bit more - helpery than most: specifically, they hide a few more of the OpenSSL - functions so that the user has an easier time writing these callbacks. - - This helper implements the client side. - """ - - def __init__(self, callback: _OCSPClientCallback[Any]) -> None: - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, cdata): # type: ignore[no-untyped-def] - try: - conn = Connection._reverse_mapping[ssl] - - # Extract the data if any was provided. - if cdata != _ffi.NULL: - data = _ffi.from_handle(cdata) - else: - data = None - - # Get the OCSP data. - ocsp_ptr = _ffi.new("unsigned char **") - ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) - if ocsp_len < 0: - # No OCSP data. - ocsp_data = b"" - else: - # Copy the OCSP data, then pass it to the callback. - ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] - - valid = callback(conn, ocsp_data, data) - - # Return 1 on success or 0 on error. - return int(bool(valid)) - - except Exception as e: - self._problems.append(e) - # Return negative value if an exception is hit. - return -1 - - self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) - - -class _CookieGenerateCallbackHelper(_CallbackExceptionHelper): - def __init__(self, callback: _CookieGenerateCallback) -> None: - _CallbackExceptionHelper.__init__(self) - - max_cookie_len = getattr(_lib, "DTLS1_COOKIE_LENGTH", 255) - - @wraps(callback) - def wrapper(ssl, out, outlen): # type: ignore[no-untyped-def] - try: - conn = Connection._reverse_mapping[ssl] - cookie = callback(conn) - if len(cookie) > max_cookie_len: - raise ValueError( - f"Cookie too long (got {len(cookie)} bytes, " - f"max {max_cookie_len})" - ) - out[0 : len(cookie)] = cookie - outlen[0] = len(cookie) - return 1 - except Exception as e: - self._problems.append(e) - # "a zero return value can be used to abort the handshake" - return 0 - - self.callback = _ffi.callback( - "int (*)(SSL *, unsigned char *, unsigned int *)", - wrapper, - ) - - -class _CookieVerifyCallbackHelper(_CallbackExceptionHelper): - def __init__(self, callback: _CookieVerifyCallback) -> None: - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, c_cookie, cookie_len): # type: ignore[no-untyped-def] - try: - conn = Connection._reverse_mapping[ssl] - return callback(conn, bytes(c_cookie[0:cookie_len])) - except Exception as e: - self._problems.append(e) - return 0 - - self.callback = _ffi.callback( - "int (*)(SSL *, unsigned char *, unsigned int)", - wrapper, - ) - - -def _asFileDescriptor(obj: Any) -> int: - fd = None - if not isinstance(obj, int): - meth = getattr(obj, "fileno", None) - if meth is not None: - obj = meth() - - if isinstance(obj, int): - fd = obj - - if not isinstance(fd, int): - raise TypeError("argument must be an int, or have a fileno() method.") - elif fd < 0: - raise ValueError( - f"file descriptor cannot be a negative integer ({fd:i})" - ) - - return fd - - -def OpenSSL_version(type: int) -> bytes: - """ - Return a string describing the version of OpenSSL in use. - - :param type: One of the :const:`OPENSSL_` constants defined in this module. - """ - return _ffi.string(_lib.OpenSSL_version(type)) - - -SSLeay_version = OpenSSL_version - - -def _make_requires(flag: int, error: str) -> Callable[[_T], _T]: - """ - Builds a decorator that ensures that functions that rely on OpenSSL - functions that are not present in this build raise NotImplementedError, - rather than AttributeError coming out of cryptography. - - :param flag: A cryptography flag that guards the functions, e.g. - ``Cryptography_HAS_NEXTPROTONEG``. - :param error: The string to be used in the exception if the flag is false. - """ - - def _requires_decorator(func): # type: ignore[no-untyped-def] - if not flag: - - @wraps(func) - def explode(*args, **kwargs): # type: ignore[no-untyped-def] - raise NotImplementedError(error) - - return explode - else: - return func - - return _requires_decorator - - -_requires_keylog = _make_requires( - getattr(_lib, "Cryptography_HAS_KEYLOG", 0), "Key logging not available" -) - -_requires_ssl_get0_group_name = _make_requires( - getattr(_lib, "Cryptography_HAS_SSL_GET0_GROUP_NAME", 0), - "Getting group name is not supported by the linked OpenSSL version", -) - -_requires_ssl_cookie = _make_requires( - getattr(_lib, "Cryptography_HAS_SSL_COOKIE", 0), - "DTLS cookie support is not available", -) - - -class Session: - """ - A class representing an SSL session. A session defines certain connection - parameters which may be re-used to speed up the setup of subsequent - connections. - - .. versionadded:: 0.14 - """ - - _session: Any - # The Context the Connection this Session came from was using. OpenSSL - # requires that a session only be re-used with a compatible SSL_CTX, but - # doesn't verify it, so we pin the Context here and enforce identity in - # Connection.set_session. - _context: Context - - -F = TypeVar("F", bound=Callable[..., Any]) - - -def _require_not_used(f: F) -> F: - @wraps(f) - def inner(self: Context, *args: Any, **kwargs: Any) -> Any: - if self._used: - raise ValueError( - "Context has already been used to create a Connection, it " - "cannot be mutated again" - ) - return f(self, *args, **kwargs) - - return typing.cast(F, inner) - - -class Context: - """ - :class:`OpenSSL.SSL.Context` instances define the parameters for setting - up new SSL connections. - - :param method: One of TLS_METHOD, TLS_CLIENT_METHOD, TLS_SERVER_METHOD, - DTLS_METHOD, DTLS_CLIENT_METHOD, or DTLS_SERVER_METHOD. - SSLv23_METHOD, TLSv1_METHOD, etc. are deprecated and should - not be used. - """ - - _methods: typing.ClassVar[ - dict[int, tuple[Callable[[], Any], int | None]] - ] = { - SSLv23_METHOD: (_lib.TLS_method, None), - TLSv1_METHOD: (_lib.TLS_method, TLS1_VERSION), - TLSv1_1_METHOD: (_lib.TLS_method, TLS1_1_VERSION), - TLSv1_2_METHOD: (_lib.TLS_method, TLS1_2_VERSION), - TLS_METHOD: (_lib.TLS_method, None), - TLS_SERVER_METHOD: (_lib.TLS_server_method, None), - TLS_CLIENT_METHOD: (_lib.TLS_client_method, None), - DTLS_METHOD: (_lib.DTLS_method, None), - DTLS_SERVER_METHOD: (_lib.DTLS_server_method, None), - DTLS_CLIENT_METHOD: (_lib.DTLS_client_method, None), - } - - def __init__(self, method: int) -> None: - if not isinstance(method, int): - raise TypeError("method must be an integer") - - try: - method_func, version = self._methods[method] - except KeyError: - raise ValueError("No such protocol") - - method_obj = method_func() - _openssl_assert(method_obj != _ffi.NULL) - - context = _lib.SSL_CTX_new(method_obj) - _openssl_assert(context != _ffi.NULL) - context = _ffi.gc(context, _lib.SSL_CTX_free) - - self._context = context - self._used = False - self._passphrase_helper: _PassphraseHelper | None = None - self._passphrase_callback: _PassphraseCallback[Any] | None = None - self._passphrase_userdata: Any | None = None - self._verify_helper: _VerifyHelper | None = None - self._verify_callback: _VerifyCallback | None = None - self._info_callback = None - self._keylog_callback = None - self._tlsext_servername_callback = None - self._app_data = None - self._alpn_select_helper: _ALPNSelectHelper | None = None - self._alpn_select_callback: _ALPNSelectCallback | None = None - self._ocsp_helper: ( - _OCSPClientCallbackHelper | _OCSPServerCallbackHelper | None - ) = None - self._ocsp_callback: ( - _OCSPClientCallback[Any] | _OCSPServerCallback[Any] | None - ) = None - self._ocsp_data: Any | None = None - self._cookie_generate_helper: _CookieGenerateCallbackHelper | None = ( - None - ) - self._cookie_verify_helper: _CookieVerifyCallbackHelper | None = None - - self.set_mode( - _lib.SSL_MODE_ENABLE_PARTIAL_WRITE - | _lib.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER - ) - if version is not None: - self.set_min_proto_version(version) - self.set_max_proto_version(version) - - @_require_not_used - def set_min_proto_version(self, version: int) -> None: - """ - Set the minimum supported protocol version. Setting the minimum - version to 0 will enable protocol versions down to the lowest version - supported by the library. - - If the underlying OpenSSL build is missing support for the selected - version, this method will raise an exception. - """ - _openssl_assert( - _lib.SSL_CTX_set_min_proto_version(self._context, version) == 1 - ) - - @_require_not_used - def set_max_proto_version(self, version: int) -> None: - """ - Set the maximum supported protocol version. Setting the maximum - version to 0 will enable protocol versions up to the highest version - supported by the library. - - If the underlying OpenSSL build is missing support for the selected - version, this method will raise an exception. - """ - _openssl_assert( - _lib.SSL_CTX_set_max_proto_version(self._context, version) == 1 - ) - - @_require_not_used - def load_verify_locations( - self, - cafile: _StrOrBytesPath | None, - capath: _StrOrBytesPath | None = None, - ) -> None: - """ - Let SSL know where we can find trusted certificates for the certificate - chain. Note that the certificates have to be in PEM format. - - If capath is passed, it must be a directory prepared using the - ``c_rehash`` tool included with OpenSSL. Either, but not both, of - *pemfile* or *capath* may be :data:`None`. - - :param cafile: In which file we can find the certificates (``bytes`` or - ``str``). - :param capath: In which directory we can find the certificates - (``bytes`` or ``str``). - - :return: None - """ - if cafile is None: - cafile = _ffi.NULL - else: - cafile = _path_bytes(cafile) - - if capath is None: - capath = _ffi.NULL - else: - capath = _path_bytes(capath) - - load_result = _lib.SSL_CTX_load_verify_locations( - self._context, cafile, capath - ) - if not load_result: - _raise_current_error() - - def _wrap_callback( - self, callback: _PassphraseCallback[_T] - ) -> _PassphraseHelper: - @wraps(callback) - def wrapper(size: int, verify: bool, userdata: Any) -> bytes: - return callback(size, verify, self._passphrase_userdata) - - return _PassphraseHelper( - FILETYPE_PEM, wrapper, more_args=True, truncate=True - ) - - @deprecated( - "Context.set_passwd_cb is deprecated. You should decrypt and load " - "your private key yourself, with cryptography's key loading APIs, " - "and then use Context.use_privatekey instead." - ) - @_require_not_used - def set_passwd_cb( - self, - callback: _PassphraseCallback[_T], - userdata: _T | None = None, - ) -> None: - """ - Set the passphrase callback. This function will be called - when a private key with a passphrase is loaded. - - :param callback: The Python callback to use. This must accept three - positional arguments. First, an integer giving the maximum length - of the passphrase it may return. If the returned passphrase is - longer than this, it will be truncated. Second, a boolean value - which will be true if the user should be prompted for the - passphrase twice and the callback should verify that the two values - supplied are equal. Third, the value given as the *userdata* - parameter to :meth:`set_passwd_cb`. The *callback* must return - a byte string. If an error occurs, *callback* should return a false - value (e.g. an empty string). - :param userdata: (optional) A Python object which will be given as - argument to the callback - :return: None - """ - if not callable(callback): - raise TypeError("callback must be callable") - - self._passphrase_helper = self._wrap_callback(callback) - self._passphrase_callback = self._passphrase_helper.callback - _lib.SSL_CTX_set_default_passwd_cb( - self._context, self._passphrase_callback - ) - self._passphrase_userdata = userdata - - @_require_not_used - def set_default_verify_paths(self) -> None: - """ - Specify that the platform provided CA certificates are to be used for - verification purposes. This method has some caveats related to the - binary wheels that cryptography (pyOpenSSL's primary dependency) ships: - - * macOS will only load certificates using this method if the user has - the ``openssl@3`` `Homebrew `_ formula installed - in the default location. - * Windows will not work. - * manylinux cryptography wheels will work on most common Linux - distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the - manylinux wheel and attempts to load roots via a fallback path. - - :return: None - """ - # SSL_CTX_set_default_verify_paths will attempt to load certs from - # both a cafile and capath that are set at compile time. However, - # it will first check environment variables and, if present, load - # those paths instead - set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) - _openssl_assert(set_result == 1) - # After attempting to set default_verify_paths we need to know whether - # to go down the fallback path. - # First we'll check to see if any env vars have been set. If so, - # we won't try to do anything else because the user has set the path - # themselves. - if not self._check_env_vars_set("SSL_CERT_DIR", "SSL_CERT_FILE"): - default_dir = _ffi.string(_lib.X509_get_default_cert_dir()) - default_file = _ffi.string(_lib.X509_get_default_cert_file()) - # Now we check to see if the default_dir and default_file are set - # to the exact values we use in our manylinux builds. If they are - # then we know to load the fallbacks - if ( - default_dir == _CRYPTOGRAPHY_MANYLINUX_CA_DIR - and default_file == _CRYPTOGRAPHY_MANYLINUX_CA_FILE - ): - # This is manylinux, let's load our fallback paths - self._fallback_default_verify_paths( - _CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS - ) - - def _check_env_vars_set(self, dir_env_var: str, file_env_var: str) -> bool: - """ - Check to see if the default cert dir/file environment vars are present. - - :return: bool - """ - return ( - os.environ.get(file_env_var) is not None - or os.environ.get(dir_env_var) is not None - ) - - def _fallback_default_verify_paths( - self, file_path: list[str], dir_path: list[str] - ) -> None: - """ - Default verify paths are based on the compiled version of OpenSSL. - However, when pyca/cryptography is compiled as a manylinux wheel - that compiled location can potentially be wrong. So, like Go, we - will try a predefined set of paths and attempt to load roots - from there. - - :return: None - """ - for cafile in file_path: - if os.path.isfile(cafile): - self.load_verify_locations(cafile) - break - - for capath in dir_path: - if os.path.isdir(capath): - self.load_verify_locations(None, capath) - break - - @_require_not_used - def use_certificate_chain_file(self, certfile: _StrOrBytesPath) -> None: - """ - Load a certificate chain from a file. - - :param certfile: The name of the certificate chain file (``bytes`` or - ``str``). Must be PEM encoded. - - :return: None - """ - certfile = _path_bytes(certfile) - - result = _lib.SSL_CTX_use_certificate_chain_file( - self._context, certfile - ) - if not result: - _raise_current_error() - - @_require_not_used - def use_certificate_file( - self, certfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM - ) -> None: - """ - Load a certificate from a file - - :param certfile: The name of the certificate file (``bytes`` or - ``str``). - :param filetype: (optional) The encoding of the file, which is either - :const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is - :const:`FILETYPE_PEM`. - - :return: None - """ - certfile = _path_bytes(certfile) - if not isinstance(filetype, int): - raise TypeError("filetype must be an integer") - - use_result = _lib.SSL_CTX_use_certificate_file( - self._context, certfile, filetype - ) - if not use_result: - _raise_current_error() - - @_require_not_used - def use_certificate(self, cert: X509 | x509.Certificate) -> None: - """ - Load a certificate from a X509 object - - :param cert: The X509 object - :return: None - """ - # Mirrored at Connection.use_certificate - if not isinstance(cert, X509): - cert = X509.from_cryptography(cert) - else: - warnings.warn( - ( - "Passing pyOpenSSL X509 objects is deprecated. You " - "should use a cryptography.x509.Certificate instead." - ), - DeprecationWarning, - stacklevel=2, - ) - - use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) - if not use_result: - _raise_current_error() - - @_require_not_used - def add_extra_chain_cert(self, certobj: X509 | x509.Certificate) -> None: - """ - Add certificate to chain - - :param certobj: The X509 certificate object to add to the chain - :return: None - """ - if not isinstance(certobj, X509): - certobj = X509.from_cryptography(certobj) - else: - warnings.warn( - ( - "Passing pyOpenSSL X509 objects is deprecated. You " - "should use a cryptography.x509.Certificate instead." - ), - DeprecationWarning, - stacklevel=2, - ) - - copy = _lib.X509_dup(certobj._x509) - add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) - if not add_result: - # TODO: This is untested. - _lib.X509_free(copy) - _raise_current_error() - - def _raise_passphrase_exception(self) -> None: - if self._passphrase_helper is not None: - self._passphrase_helper.raise_if_problem(Error) - - _raise_current_error() - - @_require_not_used - def use_privatekey_file( - self, keyfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM - ) -> None: - """ - Load a private key from a file - - :param keyfile: The name of the key file (``bytes`` or ``str``) - :param filetype: (optional) The encoding of the file, which is either - :const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is - :const:`FILETYPE_PEM`. - - :return: None - """ - keyfile = _path_bytes(keyfile) - - if not isinstance(filetype, int): - raise TypeError("filetype must be an integer") - - use_result = _lib.SSL_CTX_use_PrivateKey_file( - self._context, keyfile, filetype - ) - if not use_result: - self._raise_passphrase_exception() - - @_require_not_used - def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: - """ - Load a private key from a PKey object - - :param pkey: The PKey object - :return: None - """ - # Mirrored at Connection.use_privatekey - if not isinstance(pkey, PKey): - pkey = PKey.from_cryptography_key(pkey) - else: - warnings.warn( - ( - "Passing pyOpenSSL PKey objects is deprecated. You " - "should use a cryptography private key instead." - ), - DeprecationWarning, - stacklevel=2, - ) - - use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) - if not use_result: - self._raise_passphrase_exception() - - def check_privatekey(self) -> None: - """ - Check if the private key (loaded with :meth:`use_privatekey`) matches - the certificate (loaded with :meth:`use_certificate`) - - :return: :data:`None` (raises :exc:`Error` if something's wrong) - """ - if not _lib.SSL_CTX_check_private_key(self._context): - _raise_current_error() - - @_require_not_used - def load_client_ca(self, cafile: bytes) -> None: - """ - Load the trusted certificates that will be sent to the client. Does - not actually imply any of the certificates are trusted; that must be - configured separately. - - :param bytes cafile: The path to a certificates file in PEM format. - :return: None - """ - ca_list = _lib.SSL_load_client_CA_file( - _text_to_bytes_and_warn("cafile", cafile) - ) - _openssl_assert(ca_list != _ffi.NULL) - _lib.SSL_CTX_set_client_CA_list(self._context, ca_list) - - @_require_not_used - def set_session_id(self, buf: bytes) -> None: - """ - Set the session id to *buf* within which a session can be reused for - this Context object. This is needed when doing session resumption, - because there is no way for a stored session to know which Context - object it is associated with. - - :param bytes buf: The session id. - - :returns: None - """ - buf = _text_to_bytes_and_warn("buf", buf) - _openssl_assert( - _lib.SSL_CTX_set_session_id_context(self._context, buf, len(buf)) - == 1 - ) - - @_require_not_used - def set_session_cache_mode(self, mode: int) -> int: - """ - Set the behavior of the session cache used by all connections using - this Context. The previously set mode is returned. See - :const:`SESS_CACHE_*` for details about particular modes. - - :param mode: One or more of the SESS_CACHE_* flags (combine using - bitwise or) - :returns: The previously set caching mode. - - .. versionadded:: 0.14 - """ - if not isinstance(mode, int): - raise TypeError("mode must be an integer") - - return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) - - def get_session_cache_mode(self) -> int: - """ - Get the current session cache mode. - - :returns: The currently used cache mode. - - .. versionadded:: 0.14 - """ - return _lib.SSL_CTX_get_session_cache_mode(self._context) - - @_require_not_used - def set_verify( - self, mode: int, callback: _VerifyCallback | None = None - ) -> None: - """ - Set the verification flags for this Context object to *mode* and - specify that *callback* should be used for verification callbacks. - - :param mode: The verify mode, this should be one of - :const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If - :const:`VERIFY_PEER` is used, *mode* can be OR:ed with - :const:`VERIFY_FAIL_IF_NO_PEER_CERT` and - :const:`VERIFY_CLIENT_ONCE` to further control the behaviour. - :param callback: The optional Python verification callback to use. - This should take five arguments: A Connection object, an X509 - object, and three integer variables, which are in turn potential - error number, error depth and return code. *callback* should - return True if verification passes and False otherwise. - If omitted, OpenSSL's default verification is used. - :return: None - - See SSL_CTX_set_verify(3SSL) for further details. - """ - if not isinstance(mode, int): - raise TypeError("mode must be an integer") - - if callback is None: - self._verify_helper = None - self._verify_callback = None - _lib.SSL_CTX_set_verify(self._context, mode, _ffi.NULL) - else: - if not callable(callback): - raise TypeError("callback must be callable") - - self._verify_helper = _VerifyHelper(callback) - self._verify_callback = self._verify_helper.callback - _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) - - @_require_not_used - def set_verify_depth(self, depth: int) -> None: - """ - Set the maximum depth for the certificate chain verification that shall - be allowed for this Context object. - - :param depth: An integer specifying the verify depth - :return: None - """ - if not isinstance(depth, int): - raise TypeError("depth must be an integer") - - _lib.SSL_CTX_set_verify_depth(self._context, depth) - - def get_verify_mode(self) -> int: - """ - Retrieve the Context object's verify mode, as set by - :meth:`set_verify`. - - :return: The verify mode - """ - return _lib.SSL_CTX_get_verify_mode(self._context) - - def get_verify_depth(self) -> int: - """ - Retrieve the Context object's verify depth, as set by - :meth:`set_verify_depth`. - - :return: The verify depth - """ - return _lib.SSL_CTX_get_verify_depth(self._context) - - @_require_not_used - def load_tmp_dh(self, dhfile: _StrOrBytesPath) -> None: - """ - Load parameters for Ephemeral Diffie-Hellman - - :param dhfile: The file to load EDH parameters from (``bytes`` or - ``str``). - - :return: None - """ - dhfile = _path_bytes(dhfile) - - bio = _lib.BIO_new_file(dhfile, b"r") - if bio == _ffi.NULL: - _raise_current_error() - bio = _ffi.gc(bio, _lib.BIO_free) - - dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - dh = _ffi.gc(dh, _lib.DH_free) - res = _lib.SSL_CTX_set_tmp_dh(self._context, dh) - _openssl_assert(res == 1) - - @_require_not_used - def set_tmp_ecdh(self, curve: _EllipticCurve | ec.EllipticCurve) -> None: - """ - Select a curve to use for ECDHE key exchange. - - :param curve: A curve instance from cryptography - (:class:`~cryptogragraphy.hazmat.primitives.asymmetric.ec.EllipticCurve`). - Alternatively (deprecated) a curve object from either - :meth:`OpenSSL.crypto.get_elliptic_curve` or - :meth:`OpenSSL.crypto.get_elliptic_curves`. - - :return: None - """ - - if isinstance(curve, _EllipticCurve): - warnings.warn( - ( - "Passing pyOpenSSL elliptic curves to set_tmp_ecdh is " - "deprecated. You should use cryptography's elliptic curve " - "types instead." - ), - DeprecationWarning, - stacklevel=2, - ) - _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) - else: - name = curve.name - if name == "secp192r1": - name = "prime192v1" - elif name == "secp256r1": - name = "prime256v1" - nid = _lib.OBJ_txt2nid(name.encode()) - if nid == _lib.NID_undef: - _raise_current_error() - - ec = _lib.EC_KEY_new_by_curve_name(nid) - _openssl_assert(ec != _ffi.NULL) - ec = _ffi.gc(ec, _lib.EC_KEY_free) - _lib.SSL_CTX_set_tmp_ecdh(self._context, ec) - - @_require_not_used - def set_cipher_list(self, cipher_list: bytes) -> None: - """ - Set the list of ciphers to be used in this context. - - See the OpenSSL manual for more information (e.g. - :manpage:`ciphers(1)`). - - Note this API does not change the cipher suites used in TLS 1.3 - Use `set_tls13_ciphersuites` for that. - - :param bytes cipher_list: An OpenSSL cipher string. - :return: None - """ - cipher_list = _text_to_bytes_and_warn("cipher_list", cipher_list) - - if not isinstance(cipher_list, bytes): - raise TypeError("cipher_list must be a byte string.") - - _openssl_assert( - _lib.SSL_CTX_set_cipher_list(self._context, cipher_list) == 1 - ) - - @_require_not_used - def set_tls13_ciphersuites(self, ciphersuites: bytes) -> None: - """ - Set the list of TLS 1.3 ciphers to be used in this context. - OpenSSL maintains a separate list of TLS 1.3+ ciphers to - ciphers for TLS 1.2 and lowers. - - See the OpenSSL manual for more information (e.g. - :manpage:`ciphers(1)`). - - :param bytes ciphersuites: An OpenSSL cipher string containing - TLS 1.3+ ciphersuites. - :return: None - - .. versionadded:: 25.2.0 - """ - if not isinstance(ciphersuites, bytes): - raise TypeError("ciphersuites must be a byte string.") - - _openssl_assert( - _lib.SSL_CTX_set_ciphersuites(self._context, ciphersuites) == 1 - ) - - @deprecated( - "Context.set_client_ca_list is deprecated. X509Name support in " - "pyOpenSSL is deprecated." - ) - @_require_not_used - def set_client_ca_list( - self, certificate_authorities: Sequence[X509Name] - ) -> None: - """ - Set the list of preferred client certificate signers for this server - context. - - This list of certificate authorities will be sent to the client when - the server requests a client certificate. - - :param certificate_authorities: a sequence of X509Names. - :return: None - - .. versionadded:: 0.10 - """ - name_stack = _lib.sk_X509_NAME_new_null() - _openssl_assert(name_stack != _ffi.NULL) - - try: - for ca_name in certificate_authorities: - if not isinstance(ca_name, X509Name): - raise TypeError( - f"client CAs must be X509Name objects, not " - f"{type(ca_name).__name__} objects" - ) - copy = _lib.X509_NAME_dup(ca_name._name) - _openssl_assert(copy != _ffi.NULL) - push_result = _lib.sk_X509_NAME_push(name_stack, copy) - if not push_result: - _lib.X509_NAME_free(copy) - _raise_current_error() - except Exception: - _lib.sk_X509_NAME_free(name_stack) - raise - - _lib.SSL_CTX_set_client_CA_list(self._context, name_stack) - - @_require_not_used - def add_client_ca( - self, certificate_authority: X509 | x509.Certificate - ) -> None: - """ - Add the CA certificate to the list of preferred signers for this - context. - - The list of certificate authorities will be sent to the client when the - server requests a client certificate. - - :param certificate_authority: certificate authority's X509 certificate. - :return: None - - .. versionadded:: 0.10 - """ - if not isinstance(certificate_authority, X509): - certificate_authority = X509.from_cryptography( - certificate_authority - ) - else: - warnings.warn( - ( - "Passing pyOpenSSL X509 objects is deprecated. You " - "should use a cryptography.x509.Certificate instead." - ), - DeprecationWarning, - stacklevel=2, - ) - - add_result = _lib.SSL_CTX_add_client_CA( - self._context, certificate_authority._x509 - ) - _openssl_assert(add_result == 1) - - @_require_not_used - def set_timeout(self, timeout: int) -> None: - """ - Set the timeout for newly created sessions for this Context object to - *timeout*. The default value is 300 seconds. See the OpenSSL manual - for more information (e.g. :manpage:`SSL_CTX_set_timeout(3)`). - - :param timeout: The timeout in (whole) seconds - :return: The previous session timeout - """ - if not isinstance(timeout, int): - raise TypeError("timeout must be an integer") - - return _lib.SSL_CTX_set_timeout(self._context, timeout) - - def get_timeout(self) -> int: - """ - Retrieve session timeout, as set by :meth:`set_timeout`. The default - is 300 seconds. - - :return: The session timeout - """ - return _lib.SSL_CTX_get_timeout(self._context) - - @_require_not_used - def set_info_callback( - self, callback: Callable[[Connection, int, int], None] - ) -> None: - """ - Set the information callback to *callback*. This function will be - called from time to time during SSL handshakes. - - :param callback: The Python callback to use. This should take three - arguments: a Connection object and two integers. The first integer - specifies where in the SSL handshake the function was called, and - the other the return code from a (possibly failed) internal - function call. - :return: None - """ - - @wraps(callback) - def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def] - callback(Connection._reverse_mapping[ssl], where, return_code) - - self._info_callback = _ffi.callback( - "void (*)(const SSL *, int, int)", wrapper - ) - _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) - - @_requires_keylog - @_require_not_used - def set_keylog_callback( - self, callback: Callable[[Connection, bytes], None] - ) -> None: - """ - Set the TLS key logging callback to *callback*. This function will be - called whenever TLS key material is generated or received, in order - to allow applications to store this keying material for debugging - purposes. - - :param callback: The Python callback to use. This should take two - arguments: a Connection object and a bytestring that contains - the key material in the format used by NSS for its SSLKEYLOGFILE - debugging output. - :return: None - """ - - @wraps(callback) - def wrapper(ssl, line): # type: ignore[no-untyped-def] - line = _ffi.string(line) - callback(Connection._reverse_mapping[ssl], line) - - self._keylog_callback = _ffi.callback( - "void (*)(const SSL *, const char *)", wrapper - ) - _lib.SSL_CTX_set_keylog_callback(self._context, self._keylog_callback) - - def get_app_data(self) -> Any: - """ - Get the application data (supplied via :meth:`set_app_data()`) - - :return: The application data - """ - return self._app_data - - @_require_not_used - def set_app_data(self, data: Any) -> None: - """ - Set the application data (will be returned from get_app_data()) - - :param data: Any Python object - :return: None - """ - self._app_data = data - - def get_cert_store(self) -> X509Store | None: - """ - Get the certificate store for the context. This can be used to add - "trusted" certificates without using the - :meth:`load_verify_locations` method. - - :return: A X509Store object or None if it does not have one. - """ - store = _lib.SSL_CTX_get_cert_store(self._context) - if store == _ffi.NULL: - # TODO: This is untested. - return None - - pystore = X509Store.__new__(X509Store) - pystore._store = store - return pystore - - @_require_not_used - def set_options(self, options: int) -> int: - """ - Add options. Options set before are not cleared! - This method should be used with the :const:`OP_*` constants. - - :param options: The options to add. - :return: The new option bitmask. - """ - if not isinstance(options, int): - raise TypeError("options must be an integer") - - return _lib.SSL_CTX_set_options(self._context, options) - - @_require_not_used - def set_mode(self, mode: int) -> int: - """ - Add modes via bitmask. Modes set before are not cleared! This method - should be used with the :const:`MODE_*` constants. - - :param mode: The mode to add. - :return: The new mode bitmask. - """ - if not isinstance(mode, int): - raise TypeError("mode must be an integer") - - return _lib.SSL_CTX_set_mode(self._context, mode) - - @_require_not_used - def clear_mode(self, mode_to_clear: int) -> int: - """ - Modes previously set cannot be overwritten without being - cleared first. This method should be used to clear existing modes. - """ - return _lib.SSL_CTX_clear_mode(self._context, mode_to_clear) - - @_require_not_used - def set_tlsext_servername_callback( - self, callback: Callable[[Connection], None] - ) -> None: - """ - Specify a callback function to be called when clients specify a server - name. - - :param callback: The callback function. It will be invoked with one - argument, the Connection instance. - - .. versionadded:: 0.13 - """ - - @wraps(callback) - def wrapper(ssl, alert, arg): # type: ignore[no-untyped-def] - try: - callback(Connection._reverse_mapping[ssl]) - except Exception: - sys.excepthook(*sys.exc_info()) - return _lib.SSL_TLSEXT_ERR_ALERT_FATAL - return 0 - - self._tlsext_servername_callback = _ffi.callback( - "int (*)(SSL *, int *, void *)", wrapper - ) - _lib.SSL_CTX_set_tlsext_servername_callback( - self._context, self._tlsext_servername_callback - ) - - @_require_not_used - def set_tlsext_use_srtp(self, profiles: bytes) -> None: - """ - Enable support for negotiating SRTP keying material. - - :param bytes profiles: A colon delimited list of protection profile - names, like ``b'SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32'``. - :return: None - """ - if not isinstance(profiles, bytes): - raise TypeError("profiles must be a byte string.") - - _openssl_assert( - _lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0 - ) - - @_require_not_used - def set_alpn_protos(self, protos: list[bytes]) -> None: - """ - Specify the protocols that the client is prepared to speak after the - TLS connection has been negotiated using Application Layer Protocol - Negotiation. - - :param protos: A list of the protocols to be offered to the server. - This list should be a Python list of bytestrings representing the - protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. - """ - # Different versions of OpenSSL are inconsistent about how they handle - # empty proto lists (see #1043), so we avoid the problem entirely by - # rejecting them ourselves. - if not protos: - raise ValueError("at least one protocol must be specified") - - # Take the list of protocols and join them together, prefixing them - # with their lengths. - protostr = b"".join( - chain.from_iterable((bytes((len(p),)), p) for p in protos) - ) - - # Build a C string from the list. We don't need to save this off - # because OpenSSL immediately copies the data out. - input_str = _ffi.new("unsigned char[]", protostr) - - # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: - # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() - # return 0 on success, and non-0 on failure. - # WARNING: these functions reverse the return value convention. - _openssl_assert( - _lib.SSL_CTX_set_alpn_protos( - self._context, input_str, len(protostr) - ) - == 0 - ) - - @_require_not_used - def set_alpn_select_callback(self, callback: _ALPNSelectCallback) -> None: - """ - Specify a callback function that will be called on the server when a - client offers protocols using ALPN. - - :param callback: The callback function. It will be invoked with two - arguments: the Connection, and a list of offered protocols as - bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It can return - one of those bytestrings to indicate the chosen protocol, the - empty bytestring to terminate the TLS connection, or the - :py:obj:`NO_OVERLAPPING_PROTOCOLS` to indicate that no offered - protocol was selected, but that the connection should not be - aborted. - """ - self._alpn_select_helper = _ALPNSelectHelper(callback) - self._alpn_select_callback = self._alpn_select_helper.callback - _lib.SSL_CTX_set_alpn_select_cb( - self._context, self._alpn_select_callback, _ffi.NULL - ) - - def _set_ocsp_callback( - self, - helper: _OCSPClientCallbackHelper | _OCSPServerCallbackHelper, - data: Any | None, - ) -> None: - """ - This internal helper does the common work for - ``set_ocsp_server_callback`` and ``set_ocsp_client_callback``, which is - almost all of it. - """ - self._ocsp_helper = helper - self._ocsp_callback = helper.callback - if data is None: - self._ocsp_data = _ffi.NULL - else: - self._ocsp_data = _ffi.new_handle(data) - - rc = _lib.SSL_CTX_set_tlsext_status_cb( - self._context, self._ocsp_callback - ) - _openssl_assert(rc == 1) - rc = _lib.SSL_CTX_set_tlsext_status_arg(self._context, self._ocsp_data) - _openssl_assert(rc == 1) - - @_require_not_used - def set_ocsp_server_callback( - self, - callback: _OCSPServerCallback[_T], - data: _T | None = None, - ) -> None: - """ - Set a callback to provide OCSP data to be stapled to the TLS handshake - on the server side. - - :param callback: The callback function. It will be invoked with two - arguments: the Connection, and the optional arbitrary data you have - provided. The callback must return a bytestring that contains the - OCSP data to staple to the handshake. If no OCSP data is available - for this connection, return the empty bytestring. - :param data: Some opaque data that will be passed into the callback - function when called. This can be used to avoid needing to do - complex data lookups or to keep track of what context is being - used. This parameter is optional. - """ - helper = _OCSPServerCallbackHelper(callback) - self._set_ocsp_callback(helper, data) - - @_require_not_used - def set_ocsp_client_callback( - self, - callback: _OCSPClientCallback[_T], - data: _T | None = None, - ) -> None: - """ - Set a callback to validate OCSP data stapled to the TLS handshake on - the client side. - - :param callback: The callback function. It will be invoked with three - arguments: the Connection, a bytestring containing the stapled OCSP - assertion, and the optional arbitrary data you have provided. The - callback must return a boolean that indicates the result of - validating the OCSP data: ``True`` if the OCSP data is valid and - the certificate can be trusted, or ``False`` if either the OCSP - data is invalid or the certificate has been revoked. - :param data: Some opaque data that will be passed into the callback - function when called. This can be used to avoid needing to do - complex data lookups or to keep track of what context is being - used. This parameter is optional. - """ - helper = _OCSPClientCallbackHelper(callback) - self._set_ocsp_callback(helper, data) - - @_require_not_used - @_requires_ssl_cookie - def set_cookie_generate_callback( - self, callback: _CookieGenerateCallback - ) -> None: - self._cookie_generate_helper = _CookieGenerateCallbackHelper(callback) - _lib.SSL_CTX_set_cookie_generate_cb( - self._context, - self._cookie_generate_helper.callback, - ) - - @_require_not_used - @_requires_ssl_cookie - def set_cookie_verify_callback( - self, callback: _CookieVerifyCallback - ) -> None: - self._cookie_verify_helper = _CookieVerifyCallbackHelper(callback) - _lib.SSL_CTX_set_cookie_verify_cb( - self._context, - self._cookie_verify_helper.callback, - ) - - -class Connection: - _reverse_mapping: typing.MutableMapping[Any, Connection] = ( - WeakValueDictionary() - ) - - def __init__( - self, context: Context, socket: socket.socket | None = None - ) -> None: - """ - Create a new Connection object, using the given OpenSSL.SSL.Context - instance and socket. - - :param context: An SSL Context to use for this connection - :param socket: The socket to use for transport layer - """ - if not isinstance(context, Context): - raise TypeError("context must be a Context instance") - - context._used = True - - ssl = _lib.SSL_new(context._context) - self._ssl = _ffi.gc(ssl, _lib.SSL_free) - # We set SSL_MODE_AUTO_RETRY to handle situations where OpenSSL returns - # an SSL_ERROR_WANT_READ when processing a non-application data packet - # even though there is still data on the underlying transport. - # See https://github.com/openssl/openssl/issues/6234 for more details. - _lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY) - self._context = context - self._app_data = None - - # References to strings used for Application Layer Protocol - # Negotiation. These strings get copied at some point but it's well - # after the callback returns, so we have to hang them somewhere to - # avoid them getting freed. - self._alpn_select_callback_args: Any = None - - # Reference the verify_callback of the Context. This ensures that if - # set_verify is called again after the SSL object has been created we - # do not point to a dangling reference - self._verify_helper = context._verify_helper - self._verify_callback = context._verify_callback - - # And likewise for the cookie callbacks - self._cookie_generate_helper = context._cookie_generate_helper - self._cookie_verify_helper = context._cookie_verify_helper - - self._reverse_mapping[self._ssl] = self - - if socket is None: - self._socket = None - # Don't set up any gc for these, SSL_free will take care of them. - self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) - _openssl_assert(self._into_ssl != _ffi.NULL) - - self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) - _openssl_assert(self._from_ssl != _ffi.NULL) - - _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) - else: - self._into_ssl = None - self._from_ssl = None - self._socket = socket - set_result = _lib.SSL_set_fd( - self._ssl, _asFileDescriptor(self._socket) - ) - _openssl_assert(set_result == 1) - - def __getattr__(self, name: str) -> Any: - """ - Look up attributes on the wrapped socket object if they are not found - on the Connection object. - """ - if self._socket is None: - raise AttributeError( - f"'{self.__class__.__name__}' object has no attribute '{name}'" - ) - else: - return getattr(self._socket, name) - - def _raise_ssl_error(self, ssl: Any, result: int) -> None: - if self._context._verify_helper is not None: - self._context._verify_helper.raise_if_problem() - if self._context._alpn_select_helper is not None: - self._context._alpn_select_helper.raise_if_problem() - if self._context._ocsp_helper is not None: - self._context._ocsp_helper.raise_if_problem() - - error = _lib.SSL_get_error(ssl, result) - if error == _lib.SSL_ERROR_WANT_READ: - raise WantReadError() - elif error == _lib.SSL_ERROR_WANT_WRITE: - raise WantWriteError() - elif error == _lib.SSL_ERROR_ZERO_RETURN: - raise ZeroReturnError() - elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: - # TODO: This is untested. - raise WantX509LookupError() - elif error == _lib.SSL_ERROR_SYSCALL: - if platform == "win32": - errno = _ffi.getwinerror()[0] - else: - errno = _ffi.errno - if _lib.ERR_peek_error() == 0 or errno != 0: - if result < 0 and errno != 0: - raise SysCallError(errno, errorcode.get(errno)) - raise SysCallError(-1, "Unexpected EOF") - else: - # TODO: This is untested, but I think twisted hits it? - _raise_current_error() - elif error == _lib.SSL_ERROR_SSL and _lib.ERR_peek_error() != 0: - # In 3.0.x an unexpected EOF no longer triggers syscall error - # but we want to maintain compatibility so we check here and - # raise syscall if it is an EOF. Since we're not actually sure - # what else could raise SSL_ERROR_SSL we check for the presence - # of the OpenSSL 3 constant SSL_R_UNEXPECTED_EOF_WHILE_READING - # and if it's not present we just raise an error, which matches - # the behavior before we added this elif section - peeked_error = _lib.ERR_peek_error() - reason = _lib.ERR_GET_REASON(peeked_error) - if _lib.Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING: - _openssl_assert( - reason == _lib.SSL_R_UNEXPECTED_EOF_WHILE_READING - ) - _lib.ERR_clear_error() - raise SysCallError(-1, "Unexpected EOF") - else: - _raise_current_error() - elif error == _lib.SSL_ERROR_NONE: - pass - else: - _raise_current_error() - - def get_context(self) -> Context: - """ - Retrieve the :class:`Context` object associated with this - :class:`Connection`. - """ - return self._context - - def set_context(self, context: Context) -> None: - """ - Switch this connection to a new session context. - - :param context: A :class:`Context` instance giving the new session - context to use. - """ - if not isinstance(context, Context): - raise TypeError("context must be a Context instance") - - _lib.SSL_set_SSL_CTX(self._ssl, context._context) - self._context = context - self._context._used = True - - def set_options(self, options: int) -> int: - """ - Add options. Options set before are not cleared! - This method should be used with the :const:`OP_*` constants. - - :param options: The options to add. - :return: The new option bitmask. - """ - if not isinstance(options, int): - raise TypeError("options must be an integer") - - return _lib.SSL_set_options(self._ssl, options) - - def get_servername(self) -> bytes | None: - """ - Retrieve the servername extension value if provided in the client hello - message, or None if there wasn't one. - - :return: A byte string giving the server name or :data:`None`. - - .. versionadded:: 0.13 - """ - name = _lib.SSL_get_servername( - self._ssl, _lib.TLSEXT_NAMETYPE_host_name - ) - if name == _ffi.NULL: - return None - - return _ffi.string(name) - - def set_verify( - self, mode: int, callback: _VerifyCallback | None = None - ) -> None: - """ - Override the Context object's verification flags for this specific - connection. See :py:meth:`Context.set_verify` for details. - """ - if not isinstance(mode, int): - raise TypeError("mode must be an integer") - - if callback is None: - self._verify_helper = None - self._verify_callback = None - _lib.SSL_set_verify(self._ssl, mode, _ffi.NULL) - else: - if not callable(callback): - raise TypeError("callback must be callable") - - self._verify_helper = _VerifyHelper(callback) - self._verify_callback = self._verify_helper.callback - _lib.SSL_set_verify(self._ssl, mode, self._verify_callback) - - def get_verify_mode(self) -> int: - """ - Retrieve the Connection object's verify mode, as set by - :meth:`set_verify`. - - :return: The verify mode - """ - return _lib.SSL_get_verify_mode(self._ssl) - - def use_certificate(self, cert: X509 | x509.Certificate) -> None: - """ - Load a certificate from a X509 object - - :param cert: The X509 object - :return: None - """ - # Mirrored from Context.use_certificate - if not isinstance(cert, X509): - cert = X509.from_cryptography(cert) - else: - warnings.warn( - ( - "Passing pyOpenSSL X509 objects is deprecated. You " - "should use a cryptography.x509.Certificate instead." - ), - DeprecationWarning, - stacklevel=2, - ) - - use_result = _lib.SSL_use_certificate(self._ssl, cert._x509) - if not use_result: - _raise_current_error() - - def use_privatekey(self, pkey: _PrivateKey | PKey) -> None: - """ - Load a private key from a PKey object - - :param pkey: The PKey object - :return: None - """ - # Mirrored from Context.use_privatekey - if not isinstance(pkey, PKey): - pkey = PKey.from_cryptography_key(pkey) - else: - warnings.warn( - ( - "Passing pyOpenSSL PKey objects is deprecated. You " - "should use a cryptography private key instead." - ), - DeprecationWarning, - stacklevel=2, - ) - - use_result = _lib.SSL_use_PrivateKey(self._ssl, pkey._pkey) - if not use_result: - self._context._raise_passphrase_exception() - - def set_ciphertext_mtu(self, mtu: int) -> None: - """ - For DTLS, set the maximum UDP payload size (*not* including IP/UDP - overhead). - - Note that you might have to set :data:`OP_NO_QUERY_MTU` to prevent - OpenSSL from spontaneously clearing this. - - :param mtu: An integer giving the maximum transmission unit. - - .. versionadded:: 21.1 - """ - _lib.SSL_set_mtu(self._ssl, mtu) - - def get_cleartext_mtu(self) -> int: - """ - For DTLS, get the maximum size of unencrypted data you can pass to - :meth:`write` without exceeding the MTU (as passed to - :meth:`set_ciphertext_mtu`). - - :return: The effective MTU as an integer. - - .. versionadded:: 21.1 - """ - - if not hasattr(_lib, "DTLS_get_data_mtu"): - raise NotImplementedError("requires OpenSSL 1.1.1 or better") - return _lib.DTLS_get_data_mtu(self._ssl) - - def set_tlsext_host_name(self, name: bytes) -> None: - """ - Set the value of the servername extension to send in the client hello. - - :param name: A byte string giving the name. - - .. versionadded:: 0.13 - """ - if not isinstance(name, bytes): - raise TypeError("name must be a byte string") - elif b"\0" in name: - raise TypeError("name must not contain NUL byte") - - # XXX I guess this can fail sometimes? - _lib.SSL_set_tlsext_host_name(self._ssl, name) - - def pending(self) -> int: - """ - Get the number of bytes that can be safely read from the SSL buffer - (**not** the underlying transport buffer). - - :return: The number of bytes available in the receive buffer. - """ - return _lib.SSL_pending(self._ssl) - - def send(self, buf: _Buffer, flags: int = 0) -> int: - """ - Send data on the connection. NOTE: If you get one of the WantRead, - WantWrite or WantX509Lookup exceptions on this, you have to call the - method again with the SAME buffer. - - :param buf: The string, buffer or memoryview to send - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The number of bytes written - """ - # Backward compatibility - buf = _text_to_bytes_and_warn("buf", buf) - - with _ffi.from_buffer(buf) as data: - # check len(buf) instead of len(data) for testability - if len(buf) > 2147483647: - raise ValueError( - "Cannot send more than 2**31-1 bytes at once." - ) - - result = _lib.SSL_write(self._ssl, data, len(data)) - self._raise_ssl_error(self._ssl, result) - - return result - - write = send - - def sendall(self, buf: _Buffer, flags: int = 0) -> int: - """ - Send "all" data on the connection. This calls send() repeatedly until - all data is sent. If an error occurs, it's impossible to tell how much - data has been sent. - - :param buf: The string, buffer or memoryview to send - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The number of bytes written - """ - buf = _text_to_bytes_and_warn("buf", buf) - - with _ffi.from_buffer(buf) as data: - left_to_send = len(buf) - total_sent = 0 - - while left_to_send: - # SSL_write's num arg is an int, - # so we cannot send more than 2**31-1 bytes at once. - result = _lib.SSL_write( - self._ssl, data + total_sent, min(left_to_send, 2147483647) - ) - self._raise_ssl_error(self._ssl, result) - total_sent += result - left_to_send -= result - - return total_sent - - def recv(self, bufsiz: int, flags: int | None = None) -> bytes: - """ - Receive data on the connection. - - :param bufsiz: The maximum number of bytes to read - :param flags: (optional) The only supported flag is ``MSG_PEEK``, - all other flags are ignored. - :return: The string read from the Connection - """ - buf = _no_zero_allocator("char[]", bufsiz) - if flags is not None and flags & socket.MSG_PEEK: - result = _lib.SSL_peek(self._ssl, buf, bufsiz) - else: - result = _lib.SSL_read(self._ssl, buf, bufsiz) - self._raise_ssl_error(self._ssl, result) - return _ffi.buffer(buf, result)[:] - - read = recv - - def recv_into( - self, - buffer: Any, # collections.abc.Buffer once we use Python 3.12+ - nbytes: int | None = None, - flags: int | None = None, - ) -> int: - """ - Receive data on the connection and copy it directly into the provided - buffer, rather than creating a new string. - - :param buffer: The buffer to copy into. - :param nbytes: (optional) The maximum number of bytes to read into the - buffer. If not present, defaults to the size of the buffer. If - larger than the size of the buffer, is reduced to the size of the - buffer. - :param flags: (optional) The only supported flag is ``MSG_PEEK``, - all other flags are ignored. - :return: The number of bytes read into the buffer. - """ - if nbytes is None: - nbytes = len(buffer) - else: - nbytes = min(nbytes, len(buffer)) - - # We need to create a temporary buffer. This is annoying, it would be - # better if we could pass memoryviews straight into the SSL_read call, - # but right now we can't. Revisit this if CFFI gets that ability. - buf = _no_zero_allocator("char[]", nbytes) - if flags is not None and flags & socket.MSG_PEEK: - result = _lib.SSL_peek(self._ssl, buf, nbytes) - else: - result = _lib.SSL_read(self._ssl, buf, nbytes) - self._raise_ssl_error(self._ssl, result) - - # This strange line is all to avoid a memory copy. The buffer protocol - # should allow us to assign a CFFI buffer to the LHS of this line, but - # on CPython 3.3+ that segfaults. As a workaround, we can temporarily - # wrap it in a memoryview. - buffer[:result] = memoryview(_ffi.buffer(buf, result)) - - return result - - def _handle_bio_errors(self, bio: Any, result: int) -> typing.NoReturn: - if _lib.BIO_should_retry(bio): - if _lib.BIO_should_read(bio): - raise WantReadError() - elif _lib.BIO_should_write(bio): - # TODO: This is untested. - raise WantWriteError() - elif _lib.BIO_should_io_special(bio): - # TODO: This is untested. I think io_special means the socket - # BIO has a not-yet connected socket. - raise ValueError("BIO_should_io_special") - else: - # TODO: This is untested. - raise ValueError("unknown bio failure") - else: - # TODO: This is untested. - _raise_current_error() - - def bio_read(self, bufsiz: int) -> bytes: - """ - If the Connection was created with a memory BIO, this method can be - used to read bytes from the write end of that memory BIO. Many - Connection methods will add bytes which must be read in this manner or - the buffer will eventually fill up and the Connection will be able to - take no further actions. - - :param bufsiz: The maximum number of bytes to read - :return: The string read. - """ - if self._from_ssl is None: - raise TypeError("Connection sock was not None") - - if not isinstance(bufsiz, int): - raise TypeError("bufsiz must be an integer") - - buf = _no_zero_allocator("char[]", bufsiz) - result = _lib.BIO_read(self._from_ssl, buf, bufsiz) - if result <= 0: - self._handle_bio_errors(self._from_ssl, result) - - return _ffi.buffer(buf, result)[:] - - def bio_write(self, buf: _Buffer) -> int: - """ - If the Connection was created with a memory BIO, this method can be - used to add bytes to the read end of that memory BIO. The Connection - can then read the bytes (for example, in response to a call to - :meth:`recv`). - - :param buf: The string to put into the memory BIO. - :return: The number of bytes written - """ - buf = _text_to_bytes_and_warn("buf", buf) - - if self._into_ssl is None: - raise TypeError("Connection sock was not None") - - with _ffi.from_buffer(buf) as data: - result = _lib.BIO_write(self._into_ssl, data, len(data)) - if result <= 0: - self._handle_bio_errors(self._into_ssl, result) - return result - - def renegotiate(self) -> bool: - """ - Renegotiate the session. - - :return: True if the renegotiation can be started, False otherwise - """ - if not self.renegotiate_pending(): - _openssl_assert(_lib.SSL_renegotiate(self._ssl) == 1) - return True - return False - - def do_handshake(self) -> None: - """ - Perform an SSL handshake (usually called after :meth:`renegotiate` or - one of :meth:`set_accept_state` or :meth:`set_connect_state`). This can - raise the same exceptions as :meth:`send` and :meth:`recv`. - - :return: None. - """ - result = _lib.SSL_do_handshake(self._ssl) - self._raise_ssl_error(self._ssl, result) - - def renegotiate_pending(self) -> bool: - """ - Check if there's a renegotiation in progress, it will return False once - a renegotiation is finished. - - :return: Whether there's a renegotiation in progress - """ - return _lib.SSL_renegotiate_pending(self._ssl) == 1 - - def total_renegotiations(self) -> int: - """ - Find out the total number of renegotiations. - - :return: The number of renegotiations. - """ - return _lib.SSL_total_renegotiations(self._ssl) - - def connect(self, addr: Any) -> None: - """ - Call the :meth:`connect` method of the underlying socket and set up SSL - on the socket, using the :class:`Context` object supplied to this - :class:`Connection` object at creation. - - :param addr: A remote address - :return: What the socket's connect method returns - """ - _lib.SSL_set_connect_state(self._ssl) - return self._socket.connect(addr) # type: ignore[return-value, union-attr] - - def connect_ex(self, addr: Any) -> int: - """ - Call the :meth:`connect_ex` method of the underlying socket and set up - SSL on the socket, using the Context object supplied to this Connection - object at creation. Note that if the :meth:`connect_ex` method of the - socket doesn't return 0, SSL won't be initialized. - - :param addr: A remove address - :return: What the socket's connect_ex method returns - """ - connect_ex = self._socket.connect_ex # type: ignore[union-attr] - self.set_connect_state() - return connect_ex(addr) - - def accept(self) -> tuple[Connection, Any]: - """ - Call the :meth:`accept` method of the underlying socket and set up SSL - on the returned socket, using the Context object supplied to this - :class:`Connection` object at creation. - - :return: A *(conn, addr)* pair where *conn* is the new - :class:`Connection` object created, and *address* is as returned by - the socket's :meth:`accept`. - """ - client, addr = self._socket.accept() # type: ignore[union-attr] - conn = Connection(self._context, client) - conn.set_accept_state() - return (conn, addr) - - def DTLSv1_listen(self) -> None: - """ - Call the OpenSSL function DTLSv1_listen on this connection. See the - OpenSSL manual for more details. - - :return: None - """ - # Possible future extension: return the BIO_ADDR in some form. - bio_addr = _lib.BIO_ADDR_new() - try: - result = _lib.DTLSv1_listen(self._ssl, bio_addr) - finally: - _lib.BIO_ADDR_free(bio_addr) - # DTLSv1_listen is weird. A zero return value means 'didn't find a - # ClientHello with valid cookie, but keep trying'. So basically - # WantReadError. But it doesn't work correctly with _raise_ssl_error. - # So we raise it manually instead. - if self._cookie_generate_helper is not None: - self._cookie_generate_helper.raise_if_problem() - if self._cookie_verify_helper is not None: - self._cookie_verify_helper.raise_if_problem() - if result == 0: - raise WantReadError() - if result < 0: - self._raise_ssl_error(self._ssl, result) - - def DTLSv1_get_timeout(self) -> int | None: - """ - Determine when the DTLS SSL object next needs to perform internal - processing due to the passage of time. - - When the returned number of seconds have passed, the - :meth:`DTLSv1_handle_timeout` method needs to be called. - - :return: The time left in seconds before the next timeout or `None` - if no timeout is currently active. - """ - ptv_sec = _ffi.new("time_t *") - ptv_usec = _ffi.new("long *") - if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec): - return ptv_sec[0] + (ptv_usec[0] / 1000000) - else: - return None - - def DTLSv1_handle_timeout(self) -> bool: - """ - Handles any timeout events which have become pending on a DTLS SSL - object. - - :return: `True` if there was a pending timeout, `False` otherwise. - """ - result = _lib.DTLSv1_handle_timeout(self._ssl) - if result < 0: - self._raise_ssl_error(self._ssl, result) - assert False, "unreachable" - else: - return bool(result) - - def bio_shutdown(self) -> None: - """ - If the Connection was created with a memory BIO, this method can be - used to indicate that *end of file* has been reached on the read end of - that memory BIO. - - :return: None - """ - if self._from_ssl is None: - raise TypeError("Connection sock was not None") - - _lib.BIO_set_mem_eof_return(self._into_ssl, 0) - - def shutdown(self) -> bool: - """ - Send the shutdown message to the Connection. - - :return: True if the shutdown completed successfully (i.e. both sides - have sent closure alerts), False otherwise (in which case you - call :meth:`recv` or :meth:`send` when the connection becomes - readable/writeable). - """ - result = _lib.SSL_shutdown(self._ssl) - if result < 0: - self._raise_ssl_error(self._ssl, result) - assert False, "unreachable" - elif result > 0: - return True - else: - return False - - def get_cipher_list(self) -> list[str]: - """ - Retrieve the list of ciphers used by the Connection object. - - :return: A list of native cipher strings. - """ - ciphers = [] - for i in count(): - result = _lib.SSL_get_cipher_list(self._ssl, i) - if result == _ffi.NULL: - break - ciphers.append(_ffi.string(result).decode("utf-8")) - return ciphers - - @typing.overload - def get_client_ca_list( - self, *, as_cryptography: typing.Literal[True] - ) -> list[x509.Name]: - pass - - @typing.overload - def get_client_ca_list( - self, *, as_cryptography: typing.Literal[False] = False - ) -> list[X509Name]: - pass - - def get_client_ca_list( - self, - *, - as_cryptography: typing.Literal[True] | typing.Literal[False] = False, - ) -> list[X509Name] | list[x509.Name]: - """ - Get CAs whose certificates are suggested for client authentication. - - :param bool as_cryptography: Controls whether a list of - ``cryptography.x509.Name`` or ``OpenSSL.crypto.X509Name`` - objects should be returned. - - :return: If this is a server connection, the list of certificate - authorities that will be sent or has been sent to the client, as - controlled by this :class:`Connection`'s :class:`Context`. - - If this is a client connection, the list will be empty until the - connection with the server is established. - - .. versionadded:: 0.10 - """ - ca_names = _lib.SSL_get_client_CA_list(self._ssl) - if ca_names == _ffi.NULL: - # TODO: This is untested. - return [] - - if as_cryptography: - names = [] - for i in range(_lib.sk_X509_NAME_num(ca_names)): - name = _lib.sk_X509_NAME_value(ca_names, i) - result_buffer = _ffi.new("unsigned char**") - encode_result = _lib.i2d_X509_NAME(name, result_buffer) - _openssl_assert(encode_result >= 0) - der = _ffi.buffer(result_buffer[0], encode_result)[:] - _lib.OPENSSL_free(result_buffer[0]) - - names.append(x509.Name.from_bytes(der)) - return names - - result = [] - for i in range(_lib.sk_X509_NAME_num(ca_names)): - name = _lib.sk_X509_NAME_value(ca_names, i) - copy = _lib.X509_NAME_dup(name) - _openssl_assert(copy != _ffi.NULL) - - # Bypass X509Name.__new__, which warns that X509Name is - # deprecated -- this method is not itself deprecated. - pyname = object.__new__(X509Name) - pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) - result.append(pyname) - return result - - def makefile(self, *args: Any, **kwargs: Any) -> typing.NoReturn: - """ - The makefile() method is not implemented, since there is no dup - semantics for SSL connections - - :raise: NotImplementedError - """ - raise NotImplementedError( - "Cannot make file object of OpenSSL.SSL.Connection" - ) - - def get_app_data(self) -> Any: - """ - Retrieve application data as set by :meth:`set_app_data`. - - :return: The application data - """ - return self._app_data - - def set_app_data(self, data: Any) -> None: - """ - Set application data - - :param data: The application data - :return: None - """ - self._app_data = data - - def get_shutdown(self) -> int: - """ - Get the shutdown state of the Connection. - - :return: The shutdown state, a bitvector of SENT_SHUTDOWN, - RECEIVED_SHUTDOWN. - """ - return _lib.SSL_get_shutdown(self._ssl) - - def set_shutdown(self, state: int) -> None: - """ - Set the shutdown state of the Connection. - - :param state: bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. - :return: None - """ - if not isinstance(state, int): - raise TypeError("state must be an integer") - - _lib.SSL_set_shutdown(self._ssl, state) - - def get_state_string(self) -> bytes: - """ - Retrieve a verbose string detailing the state of the Connection. - - :return: A string representing the state - """ - return _ffi.string(_lib.SSL_state_string_long(self._ssl)) - - def server_random(self) -> bytes | None: - """ - Retrieve the random value used with the server hello message. - - :return: A string representing the state - """ - session = _lib.SSL_get_session(self._ssl) - if session == _ffi.NULL: - return None - length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) - _openssl_assert(length > 0) - outp = _no_zero_allocator("unsigned char[]", length) - _lib.SSL_get_server_random(self._ssl, outp, length) - return _ffi.buffer(outp, length)[:] - - def client_random(self) -> bytes | None: - """ - Retrieve the random value used with the client hello message. - - :return: A string representing the state - """ - session = _lib.SSL_get_session(self._ssl) - if session == _ffi.NULL: - return None - - length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) - _openssl_assert(length > 0) - outp = _no_zero_allocator("unsigned char[]", length) - _lib.SSL_get_client_random(self._ssl, outp, length) - return _ffi.buffer(outp, length)[:] - - def master_key(self) -> bytes | None: - """ - Retrieve the value of the master key for this session. - - :return: A string representing the state - """ - session = _lib.SSL_get_session(self._ssl) - if session == _ffi.NULL: - return None - - length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) - _openssl_assert(length > 0) - outp = _no_zero_allocator("unsigned char[]", length) - _lib.SSL_SESSION_get_master_key(session, outp, length) - return _ffi.buffer(outp, length)[:] - - def export_keying_material( - self, label: bytes, olen: int, context: bytes | None = None - ) -> bytes: - """ - Obtain keying material for application use. - - :param: label - a disambiguating label string as described in RFC 5705 - :param: olen - the length of the exported key material in bytes - :param: context - a per-association context value - :return: the exported key material bytes or None - """ - outp = _no_zero_allocator("unsigned char[]", olen) - context_buf = _ffi.NULL - context_len = 0 - use_context = 0 - if context is not None: - context_buf = context - context_len = len(context) - use_context = 1 - success = _lib.SSL_export_keying_material( - self._ssl, - outp, - olen, - label, - len(label), - context_buf, - context_len, - use_context, - ) - _openssl_assert(success == 1) - return _ffi.buffer(outp, olen)[:] - - def sock_shutdown(self, *args: Any, **kwargs: Any) -> None: - """ - Call the :meth:`shutdown` method of the underlying socket. - See :manpage:`shutdown(2)`. - - :return: What the socket's shutdown() method returns - """ - return self._socket.shutdown(*args, **kwargs) # type: ignore[return-value, union-attr] - - @typing.overload - def get_certificate( - self, *, as_cryptography: typing.Literal[True] - ) -> x509.Certificate | None: - pass - - @typing.overload - def get_certificate( - self, *, as_cryptography: typing.Literal[False] = False - ) -> X509 | None: - pass - - def get_certificate( - self, - *, - as_cryptography: typing.Literal[True] | typing.Literal[False] = False, - ) -> X509 | x509.Certificate | None: - """ - Retrieve the local certificate (if any) - - :param bool as_cryptography: Controls whether a - ``cryptography.x509.Certificate`` or an ``OpenSSL.crypto.X509`` - object should be returned. - - :return: The local certificate - """ - cert = _lib.SSL_get_certificate(self._ssl) - if cert != _ffi.NULL: - _lib.X509_up_ref(cert) - pycert = X509._from_raw_x509_ptr(cert) - if as_cryptography: - return pycert.to_cryptography() - return pycert - return None - - @typing.overload - def get_peer_certificate( - self, *, as_cryptography: typing.Literal[True] - ) -> x509.Certificate | None: - pass - - @typing.overload - def get_peer_certificate( - self, *, as_cryptography: typing.Literal[False] = False - ) -> X509 | None: - pass - - def get_peer_certificate( - self, - *, - as_cryptography: typing.Literal[True] | typing.Literal[False] = False, - ) -> X509 | x509.Certificate | None: - """ - Retrieve the other side's certificate (if any) - - :param bool as_cryptography: Controls whether a - ``cryptography.x509.Certificate`` or an ``OpenSSL.crypto.X509`` - object should be returned. - - :return: The peer's certificate - """ - cert = _lib.SSL_get_peer_certificate(self._ssl) - if cert != _ffi.NULL: - pycert = X509._from_raw_x509_ptr(cert) - if as_cryptography: - return pycert.to_cryptography() - return pycert - return None - - @staticmethod - def _cert_stack_to_list(cert_stack: Any) -> list[X509]: - """ - Internal helper to convert a STACK_OF(X509) to a list of X509 - instances. - """ - result = [] - for i in range(_lib.sk_X509_num(cert_stack)): - cert = _lib.sk_X509_value(cert_stack, i) - _openssl_assert(cert != _ffi.NULL) - res = _lib.X509_up_ref(cert) - _openssl_assert(res >= 1) - pycert = X509._from_raw_x509_ptr(cert) - result.append(pycert) - return result - - @staticmethod - def _cert_stack_to_cryptography_list( - cert_stack: Any, - ) -> list[x509.Certificate]: - """ - Internal helper to convert a STACK_OF(X509) to a list of X509 - instances. - """ - result = [] - for i in range(_lib.sk_X509_num(cert_stack)): - cert = _lib.sk_X509_value(cert_stack, i) - _openssl_assert(cert != _ffi.NULL) - res = _lib.X509_up_ref(cert) - _openssl_assert(res >= 1) - pycert = X509._from_raw_x509_ptr(cert) - result.append(pycert.to_cryptography()) - return result - - @typing.overload - def get_peer_cert_chain( - self, *, as_cryptography: typing.Literal[True] - ) -> list[x509.Certificate] | None: - pass - - @typing.overload - def get_peer_cert_chain( - self, *, as_cryptography: typing.Literal[False] = False - ) -> list[X509] | None: - pass - - def get_peer_cert_chain( - self, - *, - as_cryptography: typing.Literal[True] | typing.Literal[False] = False, - ) -> list[X509] | list[x509.Certificate] | None: - """ - Retrieve the other side's certificate (if any) - - :param bool as_cryptography: Controls whether a list of - ``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509`` - object should be returned. - - :return: A list of X509 instances giving the peer's certificate chain, - or None if it does not have one. - """ - cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) - if cert_stack == _ffi.NULL: - return None - - if as_cryptography: - return self._cert_stack_to_cryptography_list(cert_stack) - return self._cert_stack_to_list(cert_stack) - - @typing.overload - def get_verified_chain( - self, *, as_cryptography: typing.Literal[True] - ) -> list[x509.Certificate] | None: - pass - - @typing.overload - def get_verified_chain( - self, *, as_cryptography: typing.Literal[False] = False - ) -> list[X509] | None: - pass - - def get_verified_chain( - self, - *, - as_cryptography: typing.Literal[True] | typing.Literal[False] = False, - ) -> list[X509] | list[x509.Certificate] | None: - """ - Retrieve the verified certificate chain of the peer including the - peer's end entity certificate. It must be called after a session has - been successfully established. If peer verification was not successful - the chain may be incomplete, invalid, or None. - - :param bool as_cryptography: Controls whether a list of - ``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509`` - object should be returned. - - :return: A list of X509 instances giving the peer's verified - certificate chain, or None if it does not have one. - - .. versionadded:: 20.0 - """ - # OpenSSL 1.1+ - cert_stack = _lib.SSL_get0_verified_chain(self._ssl) - if cert_stack == _ffi.NULL: - return None - - if as_cryptography: - return self._cert_stack_to_cryptography_list(cert_stack) - return self._cert_stack_to_list(cert_stack) - - def want_read(self) -> bool: - """ - Checks if more data has to be read from the transport layer to complete - an operation. - - :return: True iff more data has to be read - """ - return _lib.SSL_want_read(self._ssl) - - def want_write(self) -> bool: - """ - Checks if there is data to write to the transport layer to complete an - operation. - - :return: True iff there is data to write - """ - return _lib.SSL_want_write(self._ssl) - - def set_accept_state(self) -> None: - """ - Set the connection to work in server mode. The handshake will be - handled automatically by read/write. - - :return: None - """ - _lib.SSL_set_accept_state(self._ssl) - - def set_connect_state(self) -> None: - """ - Set the connection to work in client mode. The handshake will be - handled automatically by read/write. - - :return: None - """ - _lib.SSL_set_connect_state(self._ssl) - - def get_session(self) -> Session | None: - """ - Returns the Session currently used. - - :return: An instance of :class:`OpenSSL.SSL.Session` or - :obj:`None` if no session exists. - - .. versionadded:: 0.14 - """ - session = _lib.SSL_get1_session(self._ssl) - if session == _ffi.NULL: - return None - - pysession = Session.__new__(Session) - pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) - pysession._context = self._context - return pysession - - def set_session(self, session: Session) -> None: - """ - Set the session to be used when the TLS/SSL connection is established. - - The session must have been obtained, via :meth:`get_session`, from a - :class:`Connection` that was using the same :class:`Context` as this - one. OpenSSL requires (but does not verify) that sessions only be - re-used with a compatible ``SSL_CTX``, so this is enforced here. - - :param session: A Session instance representing the session to use. - :returns: None - - .. versionadded:: 0.14 - """ - if not isinstance(session, Session): - raise TypeError("session must be a Session instance") - - if session._context is not self._context: - raise ValueError( - "session must have been created by a Connection using the " - "same Context as this one" - ) - - result = _lib.SSL_set_session(self._ssl, session._session) - _openssl_assert(result == 1) - - def _get_finished_message( - self, function: Callable[[Any, Any, int], int] - ) -> bytes | None: - """ - Helper to implement :meth:`get_finished` and - :meth:`get_peer_finished`. - - :param function: Either :data:`SSL_get_finished`: or - :data:`SSL_get_peer_finished`. - - :return: :data:`None` if the desired message has not yet been - received, otherwise the contents of the message. - """ - # The OpenSSL documentation says nothing about what might happen if the - # count argument given is zero. Specifically, it doesn't say whether - # the output buffer may be NULL in that case or not. Inspection of the - # implementation reveals that it calls memcpy() unconditionally. - # Section 7.1.4, paragraph 1 of the C standard suggests that - # memcpy(NULL, source, 0) is not guaranteed to produce defined (let - # alone desirable) behavior (though it probably does on just about - # every implementation...) - # - # Allocate a tiny buffer to pass in (instead of just passing NULL as - # one might expect) for the initial call so as to be safe against this - # potentially undefined behavior. - empty = _ffi.new("char[]", 0) - size = function(self._ssl, empty, 0) - if size == 0: - # No Finished message so far. - return None - - buf = _no_zero_allocator("char[]", size) - function(self._ssl, buf, size) - return _ffi.buffer(buf, size)[:] - - def get_finished(self) -> bytes | None: - """ - Obtain the latest TLS Finished message that we sent. - - :return: The contents of the message or :obj:`None` if the TLS - handshake has not yet completed. - - .. versionadded:: 0.15 - """ - return self._get_finished_message(_lib.SSL_get_finished) - - def get_peer_finished(self) -> bytes | None: - """ - Obtain the latest TLS Finished message that we received from the peer. - - :return: The contents of the message or :obj:`None` if the TLS - handshake has not yet completed. - - .. versionadded:: 0.15 - """ - return self._get_finished_message(_lib.SSL_get_peer_finished) - - def get_cipher_name(self) -> str | None: - """ - Obtain the name of the currently used cipher. - - :returns: The name of the currently used cipher or :obj:`None` - if no connection has been established. - - .. versionadded:: 0.15 - """ - cipher = _lib.SSL_get_current_cipher(self._ssl) - if cipher == _ffi.NULL: - return None - else: - name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher)) - return name.decode("utf-8") - - def get_cipher_bits(self) -> int | None: - """ - Obtain the number of secret bits of the currently used cipher. - - :returns: The number of secret bits of the currently used cipher - or :obj:`None` if no connection has been established. - - .. versionadded:: 0.15 - """ - cipher = _lib.SSL_get_current_cipher(self._ssl) - if cipher == _ffi.NULL: - return None - else: - return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL) - - def get_cipher_version(self) -> str | None: - """ - Obtain the protocol version of the currently used cipher. - - :returns: The protocol name of the currently used cipher - or :obj:`None` if no connection has been established. - - .. versionadded:: 0.15 - """ - cipher = _lib.SSL_get_current_cipher(self._ssl) - if cipher == _ffi.NULL: - return None - else: - version = _ffi.string(_lib.SSL_CIPHER_get_version(cipher)) - return version.decode("utf-8") - - def get_protocol_version_name(self) -> str: - """ - Retrieve the protocol version of the current connection. - - :returns: The TLS version of the current connection, for example - the value for TLS 1.2 would be ``TLSv1.2``or ``Unknown`` - for connections that were not successfully established. - """ - version = _ffi.string(_lib.SSL_get_version(self._ssl)) - return version.decode("utf-8") - - def get_protocol_version(self) -> int: - """ - Retrieve the SSL or TLS protocol version of the current connection. - - :returns: The TLS version of the current connection. For example, - it will return ``0x769`` for connections made over TLS version 1. - """ - version = _lib.SSL_version(self._ssl) - return version - - def set_alpn_protos(self, protos: list[bytes]) -> None: - """ - Specify the client's ALPN protocol list. - - These protocols are offered to the server during protocol negotiation. - - :param protos: A list of the protocols to be offered to the server. - This list should be a Python list of bytestrings representing the - protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. - """ - # Different versions of OpenSSL are inconsistent about how they handle - # empty proto lists (see #1043), so we avoid the problem entirely by - # rejecting them ourselves. - if not protos: - raise ValueError("at least one protocol must be specified") - - # Take the list of protocols and join them together, prefixing them - # with their lengths. - protostr = b"".join( - chain.from_iterable((bytes((len(p),)), p) for p in protos) - ) - - # Build a C string from the list. We don't need to save this off - # because OpenSSL immediately copies the data out. - input_str = _ffi.new("unsigned char[]", protostr) - - # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: - # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() - # return 0 on success, and non-0 on failure. - # WARNING: these functions reverse the return value convention. - _openssl_assert( - _lib.SSL_set_alpn_protos(self._ssl, input_str, len(protostr)) == 0 - ) - - def get_alpn_proto_negotiated(self) -> bytes: - """ - Get the protocol that was negotiated by ALPN. - - :returns: A bytestring of the protocol name. If no protocol has been - negotiated yet, returns an empty bytestring. - """ - data = _ffi.new("unsigned char **") - data_len = _ffi.new("unsigned int *") - - _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) - - if not data_len: - return b"" - - return _ffi.buffer(data[0], data_len[0])[:] - - def get_selected_srtp_profile(self) -> bytes: - """ - Get the SRTP protocol which was negotiated. - - :returns: A bytestring of the SRTP profile name. If no profile has been - negotiated yet, returns an empty bytestring. - """ - profile = _lib.SSL_get_selected_srtp_profile(self._ssl) - if not profile: - return b"" - - return _ffi.string(profile.name) - - @_requires_ssl_get0_group_name - def get_group_name(self) -> str | None: - """ - Get the name of the negotiated group for the key exchange. - - :return: A string giving the group name or :data:`None`. - """ - # Do not remove this guard. - # SSL_get0_group_name crashes with a segfault if called without - # an established connection (should return NULL but doesn't). - session = _lib.SSL_get_session(self._ssl) - if session == _ffi.NULL: - return None - - group_name = _lib.SSL_get0_group_name(self._ssl) - if group_name == _ffi.NULL: - return None - - return _ffi.string(group_name).decode("utf-8") - - def request_ocsp(self) -> None: - """ - Called to request that the server sends stapled OCSP data, if - available. If this is not called on the client side then the server - will not send OCSP data. Should be used in conjunction with - :meth:`Context.set_ocsp_client_callback`. - """ - rc = _lib.SSL_set_tlsext_status_type( - self._ssl, _lib.TLSEXT_STATUSTYPE_ocsp - ) - _openssl_assert(rc == 1) - - def set_info_callback( - self, callback: Callable[[Connection, int, int], None] - ) -> None: - """ - Set the information callback to *callback*. This function will be - called from time to time during SSL handshakes. - - :param callback: The Python callback to use. This should take three - arguments: a Connection object and two integers. The first integer - specifies where in the SSL handshake the function was called, and - the other the return code from a (possibly failed) internal - function call. - :return: None - """ - - @wraps(callback) - def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def] - callback(Connection._reverse_mapping[ssl], where, return_code) - - self._info_callback = _ffi.callback( - "void (*)(const SSL *, int, int)", wrapper - ) - _lib.SSL_set_info_callback(self._ssl, self._info_callback) diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__init__.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__init__.py deleted file mode 100644 index 7b077cf..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (C) AB Strakt -# See LICENSE for details. - -""" -pyOpenSSL - A simple wrapper around the OpenSSL library -""" - -from OpenSSL import SSL, crypto -from OpenSSL.version import ( - __author__, - __copyright__, - __email__, - __license__, - __summary__, - __title__, - __uri__, - __version__, -) - -__all__ = [ - "SSL", - "__author__", - "__copyright__", - "__email__", - "__license__", - "__summary__", - "__title__", - "__uri__", - "__version__", - "crypto", -] diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/SSL.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/SSL.cpython-311.pyc deleted file mode 100644 index 074325f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/SSL.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index bcda12f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/_util.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/_util.cpython-311.pyc deleted file mode 100644 index dd2cc50..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/_util.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/crypto.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/crypto.cpython-311.pyc deleted file mode 100644 index b1963e9..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/crypto.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/debug.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/debug.cpython-311.pyc deleted file mode 100644 index 57c9a5a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/debug.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/rand.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/rand.cpython-311.pyc deleted file mode 100644 index 97341ab..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/rand.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/version.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/version.cpython-311.pyc deleted file mode 100644 index a87b7d5..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/OpenSSL/__pycache__/version.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/_util.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/_util.py deleted file mode 100644 index 3bed359..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/_util.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import os -import sys -import warnings -from typing import Any, Callable, NoReturn, Union - -from cryptography.hazmat.bindings.openssl.binding import Binding - -StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] - -binding = Binding() -ffi = binding.ffi -lib: Any = binding.lib - - -# This is a special CFFI allocator that does not bother to zero its memory -# after allocation. This has vastly better performance on large allocations and -# so should be used whenever we don't need the memory zeroed out. -no_zero_allocator = ffi.new_allocator(should_clear_after_alloc=False) - - -def text(charp: Any) -> str: - """ - Get a native string type representing of the given CFFI ``char*`` object. - - :param charp: A C-style string represented using CFFI. - - :return: :class:`str` - """ - if not charp: - return "" - return ffi.string(charp).decode("utf-8") - - -def exception_from_error_queue(exception_type: type[Exception]) -> NoReturn: - """ - Convert an OpenSSL library failure into a Python exception. - - When a call to the native OpenSSL library fails, this is usually signalled - by the return value, and an error code is stored in an error queue - associated with the current thread. The err library provides functions to - obtain these error codes and textual error messages. - """ - errors = [] - - while True: - error = lib.ERR_get_error() - if error == 0: - break - errors.append( - ( - text(lib.ERR_lib_error_string(error)), - text(lib.ERR_func_error_string(error)), - text(lib.ERR_reason_error_string(error)), - ) - ) - - raise exception_type(errors) - - -def make_assert(error: type[Exception]) -> Callable[[bool], Any]: - """ - Create an assert function that uses :func:`exception_from_error_queue` to - raise an exception wrapped by *error*. - """ - - def openssl_assert(ok: bool) -> None: - """ - If *ok* is not True, retrieve the error from OpenSSL and raise it. - """ - if ok is not True: - exception_from_error_queue(error) - - return openssl_assert - - -def path_bytes(s: StrOrBytesPath) -> bytes: - """ - Convert a Python path to a :py:class:`bytes` for the path which can be - passed into an OpenSSL API accepting a filename. - - :param s: A path (valid for os.fspath). - - :return: An instance of :py:class:`bytes`. - """ - b = os.fspath(s) - - if isinstance(b, str): - return b.encode(sys.getfilesystemencoding()) - else: - return b - - -def byte_string(s: str) -> bytes: - return s.encode("charmap") - - -# A marker object to observe whether some optional arguments are passed any -# value or not. -UNSPECIFIED = object() - -_TEXT_WARNING = "str for {0} is no longer accepted, use bytes" - - -def text_to_bytes_and_warn(label: str, obj: Any) -> Any: - """ - If ``obj`` is text, emit a warning that it should be bytes instead and try - to convert it to bytes automatically. - - :param str label: The name of the parameter from which ``obj`` was taken - (so a developer can easily find the source of the problem and correct - it). - - :return: If ``obj`` is the text string type, a ``bytes`` object giving the - UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is - returned. - """ - if isinstance(obj, str): - warnings.warn( - _TEXT_WARNING.format(label), - category=DeprecationWarning, - stacklevel=3, - ) - return obj.encode("utf-8") - return obj diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/crypto.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/crypto.py deleted file mode 100644 index 314b577..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/crypto.py +++ /dev/null @@ -1,1950 +0,0 @@ -from __future__ import annotations - -import calendar -import datetime -import functools -import sys -import typing -from base64 import b16encode -from collections.abc import Sequence -from functools import partial -from typing import ( - Any, - Callable, - Union, -) - -if sys.version_info >= (3, 13): - from warnings import deprecated -else: - from typing_extensions import deprecated - -from cryptography import utils, x509 -from cryptography.hazmat.primitives.asymmetric import ( - dsa, - ec, - ed448, - ed25519, - rsa, -) - -from OpenSSL._util import StrOrBytesPath -from OpenSSL._util import ( - byte_string as _byte_string, -) -from OpenSSL._util import ( - exception_from_error_queue as _exception_from_error_queue, -) -from OpenSSL._util import ( - ffi as _ffi, -) -from OpenSSL._util import ( - lib as _lib, -) -from OpenSSL._util import ( - make_assert as _make_assert, -) -from OpenSSL._util import ( - path_bytes as _path_bytes, -) - -__all__ = [ - "FILETYPE_ASN1", - "FILETYPE_PEM", - "FILETYPE_TEXT", - "TYPE_DSA", - "TYPE_RSA", - "X509", - "Error", - "PKey", - "X509Name", - "X509Store", - "X509StoreContext", - "X509StoreContextError", - "X509StoreFlags", - "dump_certificate", - "dump_privatekey", - "dump_publickey", - "get_elliptic_curve", - "get_elliptic_curves", - "load_certificate", - "load_privatekey", - "load_publickey", -] - - -_PrivateKey = Union[ - dsa.DSAPrivateKey, - ec.EllipticCurvePrivateKey, - ed25519.Ed25519PrivateKey, - ed448.Ed448PrivateKey, - rsa.RSAPrivateKey, -] -_PublicKey = Union[ - dsa.DSAPublicKey, - ec.EllipticCurvePublicKey, - ed25519.Ed25519PublicKey, - ed448.Ed448PublicKey, - rsa.RSAPublicKey, -] -_Key = Union[_PrivateKey, _PublicKey] -PassphraseCallableT = Union[bytes, Callable[..., bytes]] - - -FILETYPE_PEM: int = _lib.SSL_FILETYPE_PEM -FILETYPE_ASN1: int = _lib.SSL_FILETYPE_ASN1 - -# TODO This was an API mistake. OpenSSL has no such constant. -FILETYPE_TEXT = 2**16 - 1 - -TYPE_RSA: int = _lib.EVP_PKEY_RSA -TYPE_DSA: int = _lib.EVP_PKEY_DSA -TYPE_DH: int = _lib.EVP_PKEY_DH -TYPE_EC: int = _lib.EVP_PKEY_EC - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.crypto` API. - """ - - -_raise_current_error = partial(_exception_from_error_queue, Error) -_openssl_assert = _make_assert(Error) - - -def _new_mem_buf(buffer: bytes | None = None) -> Any: - """ - Allocate a new OpenSSL memory BIO. - - Arrange for the garbage collector to clean it up automatically. - - :param buffer: None or some bytes to use to put into the BIO so that they - can be read out. - """ - if buffer is None: - bio = _lib.BIO_new(_lib.BIO_s_mem()) - free = _lib.BIO_free - else: - data = _ffi.new("char[]", buffer) - bio = _lib.BIO_new_mem_buf(data, len(buffer)) - - # Keep the memory alive as long as the bio is alive! - def free(bio: Any, ref: Any = data) -> Any: - return _lib.BIO_free(bio) - - _openssl_assert(bio != _ffi.NULL) - - bio = _ffi.gc(bio, free) - return bio - - -def _bio_to_string(bio: Any) -> bytes: - """ - Copy the contents of an OpenSSL BIO object into a Python byte string. - """ - result_buffer = _ffi.new("char**") - buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) - return _ffi.buffer(result_buffer[0], buffer_length)[:] - - -def _set_asn1_time(boundary: Any, when: bytes) -> None: - """ - The the time value of an ASN1 time object. - - @param boundary: An ASN1_TIME pointer (or an object safely - castable to that type) which will have its value set. - @param when: A string representation of the desired time value. - - @raise TypeError: If C{when} is not a L{bytes} string. - @raise ValueError: If C{when} does not represent a time in the required - format. - @raise RuntimeError: If the time value cannot be set for some other - (unspecified) reason. - """ - if not isinstance(when, bytes): - raise TypeError("when must be a byte string") - # ASN1_TIME_set_string validates the string without writing anything - # when the destination is NULL. - _openssl_assert(boundary != _ffi.NULL) - - set_result = _lib.ASN1_TIME_set_string(boundary, when) - if set_result == 0: - raise ValueError("Invalid string") - - -def _new_asn1_time(when: bytes) -> Any: - """ - Behaves like _set_asn1_time but returns a new ASN1_TIME object. - - @param when: A string representation of the desired time value. - - @raise TypeError: If C{when} is not a L{bytes} string. - @raise ValueError: If C{when} does not represent a time in the required - format. - @raise RuntimeError: If the time value cannot be set for some other - (unspecified) reason. - """ - ret = _lib.ASN1_TIME_new() - _openssl_assert(ret != _ffi.NULL) - ret = _ffi.gc(ret, _lib.ASN1_TIME_free) - _set_asn1_time(ret, when) - return ret - - -def _get_asn1_time(timestamp: Any) -> bytes | None: - """ - Retrieve the time value of an ASN1 time object. - - @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to - that type) from which the time value will be retrieved. - - @return: The time value from C{timestamp} as a L{bytes} string in a certain - format. Or C{None} if the object contains no time value. - """ - string_timestamp = _ffi.cast("ASN1_STRING*", timestamp) - if _lib.ASN1_STRING_length(string_timestamp) == 0: - return None - elif ( - _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME - ): - return _ffi.string(_lib.ASN1_STRING_get0_data(string_timestamp)) - else: - generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**") - _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp) - _openssl_assert(generalized_timestamp[0] != _ffi.NULL) - - string_timestamp = _ffi.cast("ASN1_STRING*", generalized_timestamp[0]) - string_data = _lib.ASN1_STRING_get0_data(string_timestamp) - string_result = _ffi.string(string_data) - _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) - return string_result - - -class _X509NameInvalidator: - def __init__(self) -> None: - self._names: list[X509Name] = [] - - def add(self, name: X509Name) -> None: - self._names.append(name) - - def clear(self) -> None: - for name in self._names: - # Breaks the object, but also prevents UAF! - del name._name - - -class PKey: - """ - A class representing an DSA or RSA public key or key pair. - """ - - _only_public = False - _initialized = True - - def __init__(self) -> None: - pkey = _lib.EVP_PKEY_new() - self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) - self._initialized = False - - def to_cryptography_key(self) -> _Key: - """ - Export as a ``cryptography`` key. - - :rtype: One of ``cryptography``'s `key interfaces`_. - - .. _key interfaces: https://cryptography.io/en/latest/hazmat/\ - primitives/asymmetric/rsa/#key-interfaces - - .. versionadded:: 16.1.0 - """ - from cryptography.hazmat.primitives.serialization import ( - load_der_private_key, - load_der_public_key, - ) - - if self._only_public: - der = dump_publickey(FILETYPE_ASN1, self) - return typing.cast(_Key, load_der_public_key(der)) - else: - der = _dump_privatekey_internal(FILETYPE_ASN1, self) - return typing.cast(_Key, load_der_private_key(der, password=None)) - - @classmethod - def from_cryptography_key(cls, crypto_key: _Key) -> PKey: - """ - Construct based on a ``cryptography`` *crypto_key*. - - :param crypto_key: A ``cryptography`` key. - :type crypto_key: One of ``cryptography``'s `key interfaces`_. - - :rtype: PKey - - .. versionadded:: 16.1.0 - """ - if not isinstance( - crypto_key, - ( - dsa.DSAPrivateKey, - dsa.DSAPublicKey, - ec.EllipticCurvePrivateKey, - ec.EllipticCurvePublicKey, - ed25519.Ed25519PrivateKey, - ed25519.Ed25519PublicKey, - ed448.Ed448PrivateKey, - ed448.Ed448PublicKey, - rsa.RSAPrivateKey, - rsa.RSAPublicKey, - ), - ): - raise TypeError("Unsupported key type") - - from cryptography.hazmat.primitives.serialization import ( - Encoding, - NoEncryption, - PrivateFormat, - PublicFormat, - ) - - if isinstance( - crypto_key, - ( - dsa.DSAPublicKey, - ec.EllipticCurvePublicKey, - ed25519.Ed25519PublicKey, - ed448.Ed448PublicKey, - rsa.RSAPublicKey, - ), - ): - return load_publickey( - FILETYPE_ASN1, - crypto_key.public_bytes( - Encoding.DER, PublicFormat.SubjectPublicKeyInfo - ), - ) - else: - der = crypto_key.private_bytes( - Encoding.DER, PrivateFormat.PKCS8, NoEncryption() - ) - return load_privatekey(FILETYPE_ASN1, der) - - @deprecated( - "PKey.generate_key is deprecated. You should use the key " - "generation APIs in cryptography instead." - ) - def generate_key(self, type: int, bits: int) -> None: - """ - Generate a key pair of the given type, with the given number of bits. - - This generates a key "into" the this object. - - :param type: The key type. - :type type: :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` - :param bits: The number of bits. - :type bits: :py:data:`int` ``>= 0`` - :raises TypeError: If :py:data:`type` or :py:data:`bits` isn't - of the appropriate type. - :raises ValueError: If the number of bits isn't an integer of - the appropriate size. - :return: ``None`` - """ - if not isinstance(type, int): - raise TypeError("type must be an integer") - - if not isinstance(bits, int): - raise TypeError("bits must be an integer") - - if type == TYPE_RSA: - if bits <= 0: - raise ValueError("Invalid number of bits") - - # TODO Check error return - exponent = _lib.BN_new() - exponent = _ffi.gc(exponent, _lib.BN_free) - _lib.BN_set_word(exponent, _lib.RSA_F4) - - rsa = _lib.RSA_new() - - result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL) - _openssl_assert(result == 1) - - result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa) - _openssl_assert(result == 1) - - elif type == TYPE_DSA: - dsa = _lib.DSA_new() - _openssl_assert(dsa != _ffi.NULL) - - dsa = _ffi.gc(dsa, _lib.DSA_free) - res = _lib.DSA_generate_parameters_ex( - dsa, bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL - ) - _openssl_assert(res == 1) - - _openssl_assert(_lib.DSA_generate_key(dsa) == 1) - _openssl_assert(_lib.EVP_PKEY_set1_DSA(self._pkey, dsa) == 1) - else: - raise Error("No such key type") - - self._initialized = True - - @deprecated( - "PKey.check is deprecated. You should use the APIs in " - "cryptography instead." - ) - def check(self) -> bool: - """ - Check the consistency of an RSA private key. - - This is the Python equivalent of OpenSSL's ``RSA_check_key``. - - :return: ``True`` if key is consistent. - - :raise OpenSSL.crypto.Error: if the key is inconsistent. - - :raise TypeError: if the key is of a type which cannot be checked. - Only RSA keys can currently be checked. - """ - if self._only_public: - raise TypeError("public key only") - - if _lib.EVP_PKEY_type(self.type()) != _lib.EVP_PKEY_RSA: - raise TypeError("Only RSA keys can currently be checked.") - - rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) - rsa = _ffi.gc(rsa, _lib.RSA_free) - result = _lib.RSA_check_key(rsa) - if result == 1: - return True - _raise_current_error() - - def type(self) -> int: - """ - Returns the type of the key - - :return: The type of the key. - """ - return _lib.EVP_PKEY_id(self._pkey) - - def bits(self) -> int: - """ - Returns the number of bits of the key - - :return: The number of bits of the key. - """ - return _lib.EVP_PKEY_bits(self._pkey) - - -class _EllipticCurve: - """ - A representation of a supported elliptic curve. - - @cvar _curves: :py:obj:`None` until an attempt is made to load the curves. - Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve` - instances each of which represents one curve supported by the system. - @type _curves: :py:type:`NoneType` or :py:type:`set` - """ - - _curves = None - - def __ne__(self, other: Any) -> bool: - """ - Implement cooperation with the right-hand side argument of ``!=``. - - Python 3 seems to have dropped this cooperation in this very narrow - circumstance. - """ - if isinstance(other, _EllipticCurve): - return super().__ne__(other) - return NotImplemented - - @classmethod - def _load_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]: - """ - Get the curves supported by OpenSSL. - - :param lib: The OpenSSL library binding object. - - :return: A :py:type:`set` of ``cls`` instances giving the names of the - elliptic curves the underlying library supports. - """ - num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) - builtin_curves = _ffi.new("EC_builtin_curve[]", num_curves) - # The return value on this call should be num_curves again. We - # could check it to make sure but if it *isn't* then.. what could - # we do? Abort the whole process, I suppose...? -exarkun - lib.EC_get_builtin_curves(builtin_curves, num_curves) - return set(cls.from_nid(lib, c.nid) for c in builtin_curves) - - @classmethod - def _get_elliptic_curves(cls, lib: Any) -> set[_EllipticCurve]: - """ - Get, cache, and return the curves supported by OpenSSL. - - :param lib: The OpenSSL library binding object. - - :return: A :py:type:`set` of ``cls`` instances giving the names of the - elliptic curves the underlying library supports. - """ - if cls._curves is None: - cls._curves = cls._load_elliptic_curves(lib) - return cls._curves - - @classmethod - def from_nid(cls, lib: Any, nid: int) -> _EllipticCurve: - """ - Instantiate a new :py:class:`_EllipticCurve` associated with the given - OpenSSL NID. - - :param lib: The OpenSSL library binding object. - - :param nid: The OpenSSL NID the resulting curve object will represent. - This must be a curve NID (and not, for example, a hash NID) or - subsequent operations will fail in unpredictable ways. - :type nid: :py:class:`int` - - :return: The curve object. - """ - return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) - - def __init__(self, lib: Any, nid: int, name: str) -> None: - """ - :param _lib: The :py:mod:`cryptography` binding instance used to - interface with OpenSSL. - - :param _nid: The OpenSSL NID identifying the curve this object - represents. - :type _nid: :py:class:`int` - - :param name: The OpenSSL short name identifying the curve this object - represents. - :type name: :py:class:`unicode` - """ - self._lib = lib - self._nid = nid - self.name = name - - def __repr__(self) -> str: - return f"" - - def _to_EC_KEY(self) -> Any: - """ - Create a new OpenSSL EC_KEY structure initialized to use this curve. - - The structure is automatically garbage collected when the Python object - is garbage collected. - """ - key = self._lib.EC_KEY_new_by_curve_name(self._nid) - return _ffi.gc(key, _lib.EC_KEY_free) - - -@deprecated( - "get_elliptic_curves is deprecated. You should use the APIs in " - "cryptography instead." -) -def get_elliptic_curves() -> set[_EllipticCurve]: - """ - Return a set of objects representing the elliptic curves supported in the - OpenSSL build in use. - - The curve objects have a :py:class:`unicode` ``name`` attribute by which - they identify themselves. - - The curve objects are useful as values for the argument accepted by - :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be - used for ECDHE key exchange. - """ - return _EllipticCurve._get_elliptic_curves(_lib) - - -@deprecated( - "get_elliptic_curve is deprecated. You should use the APIs in " - "cryptography instead." -) -def get_elliptic_curve(name: str) -> _EllipticCurve: - """ - Return a single curve object selected by name. - - See :py:func:`get_elliptic_curves` for information about curve objects. - - :param name: The OpenSSL short name identifying the curve object to - retrieve. - :type name: :py:class:`unicode` - - If the named curve is not supported then :py:class:`ValueError` is raised. - """ - for curve in get_elliptic_curves(): - if curve.name == name: - return curve - raise ValueError("unknown curve name", name) - - -@deprecated( - "X509Name support in pyOpenSSL is deprecated. You should use the " - "APIs in cryptography." -) -@functools.total_ordering -class X509Name: - """ - An X.509 Distinguished Name. - - :ivar countryName: The country of the entity. - :ivar C: Alias for :py:attr:`countryName`. - - :ivar stateOrProvinceName: The state or province of the entity. - :ivar ST: Alias for :py:attr:`stateOrProvinceName`. - - :ivar localityName: The locality of the entity. - :ivar L: Alias for :py:attr:`localityName`. - - :ivar organizationName: The organization name of the entity. - :ivar O: Alias for :py:attr:`organizationName`. - - :ivar organizationalUnitName: The organizational unit of the entity. - :ivar OU: Alias for :py:attr:`organizationalUnitName` - - :ivar commonName: The common name of the entity. - :ivar CN: Alias for :py:attr:`commonName`. - - :ivar emailAddress: The e-mail address of the entity. - """ - - def __init__(self, name: X509Name) -> None: - """ - Create a new X509Name, copying the given X509Name instance. - - :param name: The name to copy. - :type name: :py:class:`X509Name` - """ - name = _lib.X509_NAME_dup(name._name) - self._name: Any = _ffi.gc(name, _lib.X509_NAME_free) - - def __setattr__(self, name: str, value: Any) -> None: - if name.startswith("_"): - return super().__setattr__(name, value) - - # Note: we really do not want str subclasses here, so we do not use - # isinstance. - if type(name) is not str: - raise TypeError( - f"attribute name must be string, not " - f"'{type(value).__name__:.200}'" - ) - - nid = _lib.OBJ_txt2nid(_byte_string(name)) - if nid == _lib.NID_undef: - try: - _raise_current_error() - except Error: - pass - raise AttributeError("No such attribute") - - # If there's an old entry for this NID, remove it - for i in range(_lib.X509_NAME_entry_count(self._name)): - ent = _lib.X509_NAME_get_entry(self._name, i) - ent_obj = _lib.X509_NAME_ENTRY_get_object(ent) - ent_nid = _lib.OBJ_obj2nid(ent_obj) - if nid == ent_nid: - ent = _lib.X509_NAME_delete_entry(self._name, i) - _lib.X509_NAME_ENTRY_free(ent) - break - - if isinstance(value, str): - value = value.encode("utf-8") - - add_result = _lib.X509_NAME_add_entry_by_NID( - self._name, nid, _lib.MBSTRING_UTF8, value, len(value), -1, 0 - ) - if not add_result: - _raise_current_error() - - def __getattr__(self, name: str) -> str | None: - """ - Find attribute. An X509Name object has the following attributes: - countryName (alias C), stateOrProvince (alias ST), locality (alias L), - organization (alias O), organizationalUnit (alias OU), commonName - (alias CN) and more... - """ - nid = _lib.OBJ_txt2nid(_byte_string(name)) - if nid == _lib.NID_undef: - # This is a bit weird. OBJ_txt2nid indicated failure, but it seems - # a lower level function, a2d_ASN1_OBJECT, also feels the need to - # push something onto the error queue. If we don't clean that up - # now, someone else will bump into it later and be quite confused. - # See lp#314814. - try: - _raise_current_error() - except Error: - pass - raise AttributeError("No such attribute") - - entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1) - if entry_index == -1: - return None - - entry = _lib.X509_NAME_get_entry(self._name, entry_index) - data = _lib.X509_NAME_ENTRY_get_data(entry) - - result_buffer = _ffi.new("unsigned char**") - data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data) - _openssl_assert(data_length >= 0) - - try: - result = _ffi.buffer(result_buffer[0], data_length)[:].decode( - "utf-8" - ) - finally: - # XXX untested - _lib.OPENSSL_free(result_buffer[0]) - return result - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, X509Name): - return NotImplemented - - return _lib.X509_NAME_cmp(self._name, other._name) == 0 - - def __lt__(self, other: Any) -> bool: - if not isinstance(other, X509Name): - return NotImplemented - - return _lib.X509_NAME_cmp(self._name, other._name) < 0 - - def __repr__(self) -> str: - """ - String representation of an X509Name - """ - result_buffer = _ffi.new("char[]", 512) - format_result = _lib.X509_NAME_oneline( - self._name, result_buffer, len(result_buffer) - ) - _openssl_assert(format_result != _ffi.NULL) - - return "".format( - _ffi.string(result_buffer).decode("utf-8"), - ) - - def hash(self) -> int: - """ - Return an integer representation of the first four bytes of the - MD5 digest of the DER representation of the name. - - This is the Python equivalent of OpenSSL's ``X509_NAME_hash``. - - :return: The (integer) hash of this name. - :rtype: :py:class:`int` - """ - return _lib.X509_NAME_hash(self._name) - - def der(self) -> bytes: - """ - Return the DER encoding of this name. - - :return: The DER encoded form of this name. - :rtype: :py:class:`bytes` - """ - result_buffer = _ffi.new("unsigned char**") - encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) - _openssl_assert(encode_result >= 0) - - string_result = _ffi.buffer(result_buffer[0], encode_result)[:] - _lib.OPENSSL_free(result_buffer[0]) - return string_result - - def get_components(self) -> list[tuple[bytes, bytes]]: - """ - Returns the components of this name, as a sequence of 2-tuples. - - :return: The components of this name. - :rtype: :py:class:`list` of ``name, value`` tuples. - """ - result = [] - for i in range(_lib.X509_NAME_entry_count(self._name)): - ent = _lib.X509_NAME_get_entry(self._name, i) - - fname = _lib.X509_NAME_ENTRY_get_object(ent) - fval = _lib.X509_NAME_ENTRY_get_data(ent) - - nid = _lib.OBJ_obj2nid(fname) - name = _lib.OBJ_nid2sn(nid) - - # ffi.string does not handle strings containing NULL bytes - # (which may have been generated by old, broken software) - value = _ffi.buffer( - _lib.ASN1_STRING_get0_data(fval), _lib.ASN1_STRING_length(fval) - )[:] - result.append((_ffi.string(name), value)) - - return result - - -class X509: - """ - An X.509 certificate. - """ - - def __init__(self) -> None: - x509 = _lib.X509_new() - _openssl_assert(x509 != _ffi.NULL) - self._x509 = _ffi.gc(x509, _lib.X509_free) - - self._issuer_invalidator = _X509NameInvalidator() - self._subject_invalidator = _X509NameInvalidator() - - @classmethod - def _from_raw_x509_ptr(cls, x509: Any) -> X509: - cert = cls.__new__(cls) - cert._x509 = _ffi.gc(x509, _lib.X509_free) - cert._issuer_invalidator = _X509NameInvalidator() - cert._subject_invalidator = _X509NameInvalidator() - return cert - - def to_cryptography(self) -> x509.Certificate: - """ - Export as a ``cryptography`` certificate. - - :rtype: ``cryptography.x509.Certificate`` - - .. versionadded:: 17.1.0 - """ - from cryptography.x509 import load_der_x509_certificate - - der = dump_certificate(FILETYPE_ASN1, self) - return load_der_x509_certificate(der) - - @classmethod - def from_cryptography(cls, crypto_cert: x509.Certificate) -> X509: - """ - Construct based on a ``cryptography`` *crypto_cert*. - - :param crypto_key: A ``cryptography`` X.509 certificate. - :type crypto_key: ``cryptography.x509.Certificate`` - - :rtype: X509 - - .. versionadded:: 17.1.0 - """ - if not isinstance(crypto_cert, x509.Certificate): - raise TypeError("Must be a certificate") - - from cryptography.hazmat.primitives.serialization import Encoding - - der = crypto_cert.public_bytes(Encoding.DER) - return load_certificate(FILETYPE_ASN1, der) - - @deprecated( - "X509.set_version is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_version(self, version: int) -> None: - """ - Set the version number of the certificate. Note that the - version value is zero-based, eg. a value of 0 is V1. - - :param version: The version number of the certificate. - :type version: :py:class:`int` - - :return: ``None`` - """ - if not isinstance(version, int): - raise TypeError("version must be an integer") - - _openssl_assert(_lib.X509_set_version(self._x509, version) == 1) - - def get_version(self) -> int: - """ - Return the version number of the certificate. - - :return: The version number of the certificate. - :rtype: :py:class:`int` - """ - return _lib.X509_get_version(self._x509) - - def get_pubkey(self) -> PKey: - """ - Get the public key of the certificate. - - :return: The public key. - :rtype: :py:class:`PKey` - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.X509_get_pubkey(self._x509) - if pkey._pkey == _ffi.NULL: - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - @deprecated( - "X509.set_pubkey is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_pubkey(self, pkey: PKey) -> None: - """ - Set the public key of the certificate. - - :param pkey: The public key. - :type pkey: :py:class:`PKey` - - :return: :py:data:`None` - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey) - _openssl_assert(set_result == 1) - - @deprecated( - "X509.sign is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def sign(self, pkey: PKey, digest: str) -> None: - """ - Sign the certificate with this key and digest type. - - :param pkey: The key to sign with. - :type pkey: :py:class:`PKey` - - :param digest: The name of the message digest to use. - :type digest: :py:class:`str` - - :return: :py:data:`None` - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - if pkey._only_public: - raise ValueError("Key only has public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - evp_md = _lib.EVP_get_digestbyname(_byte_string(digest)) - if evp_md == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md) - _openssl_assert(sign_result > 0) - - def get_signature_algorithm(self) -> bytes: - """ - Return the signature algorithm used in the certificate. - - :return: The name of the algorithm. - :rtype: :py:class:`bytes` - - :raises ValueError: If the signature algorithm is undefined. - - .. versionadded:: 0.13 - """ - sig_alg = _lib.X509_get0_tbs_sigalg(self._x509) - alg = _ffi.new("ASN1_OBJECT **") - _lib.X509_ALGOR_get0(alg, _ffi.NULL, _ffi.NULL, sig_alg) - nid = _lib.OBJ_obj2nid(alg[0]) - if nid == _lib.NID_undef: - raise ValueError("Undefined signature algorithm") - return _ffi.string(_lib.OBJ_nid2ln(nid)) - - def digest(self, digest_name: str) -> bytes: - """ - Return the digest of the X509 object. - - :param digest_name: The name of the digest algorithm to use. - :type digest_name: :py:class:`str` - - :return: The digest of the object, formatted as - :py:const:`b":"`-delimited hex pairs. - :rtype: :py:class:`bytes` - """ - digest = _lib.EVP_get_digestbyname(_byte_string(digest_name)) - if digest == _ffi.NULL: - raise ValueError("No such digest method") - - result_buffer = _ffi.new("unsigned char[]", _lib.EVP_MAX_MD_SIZE) - result_length = _ffi.new("unsigned int[]", 1) - result_length[0] = len(result_buffer) - - digest_result = _lib.X509_digest( - self._x509, digest, result_buffer, result_length - ) - _openssl_assert(digest_result == 1) - - return b":".join( - [ - b16encode(ch).upper() - for ch in _ffi.buffer(result_buffer, result_length[0]) - ] - ) - - def subject_name_hash(self) -> int: - """ - Return the hash of the X509 subject. - - :return: The hash of the subject. - :rtype: :py:class:`int` - """ - return _lib.X509_subject_name_hash(self._x509) - - @deprecated( - "X509.set_serial_number is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_serial_number(self, serial: int) -> None: - """ - Set the serial number of the certificate. - - :param serial: The new serial number. - :type serial: :py:class:`int` - - :return: :py:data`None` - """ - if not isinstance(serial, int): - raise TypeError("serial must be an integer") - - hex_serial = hex(serial)[2:] - hex_serial_bytes = hex_serial.encode("ascii") - - bignum_serial = _ffi.new("BIGNUM**") - - # BN_hex2bn stores the result in &bignum. - result = _lib.BN_hex2bn(bignum_serial, hex_serial_bytes) - _openssl_assert(result != _ffi.NULL) - - asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL) - _lib.BN_free(bignum_serial[0]) - _openssl_assert(asn1_serial != _ffi.NULL) - asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free) - set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial) - _openssl_assert(set_result == 1) - - def get_serial_number(self) -> int: - """ - Return the serial number of this certificate. - - :return: The serial number. - :rtype: int - """ - asn1_serial = _lib.X509_get_serialNumber(self._x509) - bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) - try: - hex_serial = _lib.BN_bn2hex(bignum_serial) - try: - hexstring_serial = _ffi.string(hex_serial) - serial = int(hexstring_serial, 16) - return serial - finally: - _lib.OPENSSL_free(hex_serial) - finally: - _lib.BN_free(bignum_serial) - - @deprecated( - "X509.gmtime_adj_notAfter is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def gmtime_adj_notAfter(self, amount: int) -> None: - """ - Adjust the time stamp on which the certificate stops being valid. - - :param int amount: The number of seconds by which to adjust the - timestamp. - :return: ``None`` - """ - if not isinstance(amount, int): - raise TypeError("amount must be an integer") - - notAfter = _lib.X509_getm_notAfter(self._x509) - _lib.X509_gmtime_adj(notAfter, amount) - - @deprecated( - "X509.gmtime_adj_notBefore is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def gmtime_adj_notBefore(self, amount: int) -> None: - """ - Adjust the timestamp on which the certificate starts being valid. - - :param amount: The number of seconds by which to adjust the timestamp. - :return: ``None`` - """ - if not isinstance(amount, int): - raise TypeError("amount must be an integer") - - notBefore = _lib.X509_getm_notBefore(self._x509) - _lib.X509_gmtime_adj(notBefore, amount) - - def has_expired(self) -> bool: - """ - Check whether the certificate has expired. - - :return: ``True`` if the certificate has expired, ``False`` otherwise. - :rtype: bool - """ - time_bytes = self.get_notAfter() - if time_bytes is None: - raise ValueError("Unable to determine notAfter") - time_string = time_bytes.decode("utf-8") - not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") - - UTC = datetime.timezone.utc - utcnow = datetime.datetime.now(UTC).replace(tzinfo=None) - return not_after < utcnow - - def _get_boundary_time(self, which: Any) -> bytes | None: - return _get_asn1_time(which(self._x509)) - - def get_notBefore(self) -> bytes | None: - """ - Get the timestamp at which the certificate starts being valid. - - The timestamp is formatted as an ASN.1 TIME:: - - YYYYMMDDhhmmssZ - - :return: A timestamp string, or ``None`` if there is none. - :rtype: bytes or NoneType - """ - return self._get_boundary_time(_lib.X509_getm_notBefore) - - def _set_boundary_time( - self, which: Callable[..., Any], when: bytes - ) -> None: - return _set_asn1_time(which(self._x509), when) - - @deprecated( - "X509.set_notBefore is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_notBefore(self, when: bytes) -> None: - """ - Set the timestamp at which the certificate starts being valid. - - The timestamp is formatted as an ASN.1 TIME:: - - YYYYMMDDhhmmssZ - - :param bytes when: A timestamp string. - :return: ``None`` - """ - return self._set_boundary_time(_lib.X509_getm_notBefore, when) - - def get_notAfter(self) -> bytes | None: - """ - Get the timestamp at which the certificate stops being valid. - - The timestamp is formatted as an ASN.1 TIME:: - - YYYYMMDDhhmmssZ - - :return: A timestamp string, or ``None`` if there is none. - :rtype: bytes or NoneType - """ - return self._get_boundary_time(_lib.X509_getm_notAfter) - - @deprecated( - "X509.set_notAfter is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_notAfter(self, when: bytes) -> None: - """ - Set the timestamp at which the certificate stops being valid. - - The timestamp is formatted as an ASN.1 TIME:: - - YYYYMMDDhhmmssZ - - :param bytes when: A timestamp string. - :return: ``None`` - """ - return self._set_boundary_time(_lib.X509_getm_notAfter, when) - - def _get_name(self, which: Any) -> X509Name: - # Bypass X509Name.__new__, which warns that X509Name is deprecated; - # callers that should warn are decorated individually. - name = object.__new__(X509Name) - name._name = which(self._x509) - _openssl_assert(name._name != _ffi.NULL) - - # The name is owned by the X509 structure. As long as the X509Name - # Python object is alive, keep the X509 Python object alive. - name._owner = self - - return name - - def _set_name(self, which: Any, name: X509Name) -> None: - if not isinstance(name, X509Name): - raise TypeError("name must be an X509Name") - set_result = which(self._x509, name._name) - _openssl_assert(set_result == 1) - - @deprecated( - "X509.get_issuer is deprecated. You should use " - "cryptography's X.509 APIs instead." - ) - def get_issuer(self) -> X509Name: - """ - Return the issuer of this certificate. - - This creates a new :class:`X509Name` that wraps the underlying issuer - name field on the certificate. Modifying it will modify the underlying - certificate, and will have the effect of modifying any other - :class:`X509Name` that refers to this issuer. - - :return: The issuer of this certificate. - :rtype: :class:`X509Name` - """ - name = self._get_name(_lib.X509_get_issuer_name) - self._issuer_invalidator.add(name) - return name - - @deprecated( - "X509.set_issuer is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_issuer(self, issuer: X509Name) -> None: - """ - Set the issuer of this certificate. - - :param issuer: The issuer. - :type issuer: :py:class:`X509Name` - - :return: ``None`` - """ - self._set_name(_lib.X509_set_issuer_name, issuer) - self._issuer_invalidator.clear() - - @deprecated( - "X509.get_subject is deprecated. You should use " - "cryptography's X.509 APIs instead." - ) - def get_subject(self) -> X509Name: - """ - Return the subject of this certificate. - - This creates a new :class:`X509Name` that wraps the underlying subject - name field on the certificate. Modifying it will modify the underlying - certificate, and will have the effect of modifying any other - :class:`X509Name` that refers to this subject. - - :return: The subject of this certificate. - :rtype: :class:`X509Name` - """ - name = self._get_name(_lib.X509_get_subject_name) - self._subject_invalidator.add(name) - return name - - @deprecated( - "X509.set_subject is deprecated. You should use " - "cryptography's CertificateBuilder instead." - ) - def set_subject(self, subject: X509Name) -> None: - """ - Set the subject of this certificate. - - :param subject: The subject. - :type subject: :py:class:`X509Name` - - :return: ``None`` - """ - self._set_name(_lib.X509_set_subject_name, subject) - self._subject_invalidator.clear() - - def get_extension_count(self) -> int: - """ - Get the number of extensions on this certificate. - - :return: The number of extensions. - :rtype: :py:class:`int` - - .. versionadded:: 0.12 - """ - return _lib.X509_get_ext_count(self._x509) - - -class X509StoreFlags: - """ - Flags for X509 verification, used to change the behavior of - :class:`X509Store`. - - See `OpenSSL Verification Flags`_ for details. - - .. _OpenSSL Verification Flags: - https://www.openssl.org/docs/manmaster/man3/X509_VERIFY_PARAM_set_flags.html - """ - - CRL_CHECK: int = _lib.X509_V_FLAG_CRL_CHECK - CRL_CHECK_ALL: int = _lib.X509_V_FLAG_CRL_CHECK_ALL - IGNORE_CRITICAL: int = _lib.X509_V_FLAG_IGNORE_CRITICAL - X509_STRICT: int = _lib.X509_V_FLAG_X509_STRICT - ALLOW_PROXY_CERTS: int = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS - POLICY_CHECK: int = _lib.X509_V_FLAG_POLICY_CHECK - EXPLICIT_POLICY: int = _lib.X509_V_FLAG_EXPLICIT_POLICY - INHIBIT_MAP: int = _lib.X509_V_FLAG_INHIBIT_MAP - CHECK_SS_SIGNATURE: int = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE - PARTIAL_CHAIN: int = _lib.X509_V_FLAG_PARTIAL_CHAIN - - -class X509Store: - """ - An X.509 store. - - An X.509 store is used to describe a context in which to verify a - certificate. A description of a context may include a set of certificates - to trust, a set of certificate revocation lists, verification flags and - more. - - An X.509 store, being only a description, cannot be used by itself to - verify a certificate. To carry out the actual verification process, see - :class:`X509StoreContext`. - """ - - def __init__(self) -> None: - store = _lib.X509_STORE_new() - self._store = _ffi.gc(store, _lib.X509_STORE_free) - - def add_cert(self, cert: X509) -> None: - """ - Adds a trusted certificate to this store. - - Adding a certificate with this method adds this certificate as a - *trusted* certificate. - - :param X509 cert: The certificate to add to this store. - - :raises TypeError: If the certificate is not an :class:`X509`. - - :raises OpenSSL.crypto.Error: If OpenSSL was unhappy with your - certificate. - - :return: ``None`` if the certificate was added successfully. - """ - if not isinstance(cert, X509): - raise TypeError() - - res = _lib.X509_STORE_add_cert(self._store, cert._x509) - _openssl_assert(res == 1) - - def add_crl(self, crl: x509.CertificateRevocationList) -> None: - """ - Add a certificate revocation list to this store. - - The certificate revocation lists added to a store will only be used if - the associated flags are configured to check certificate revocation - lists. - - .. versionadded:: 16.1.0 - - :param crl: The certificate revocation list to add to this store. - :type crl: ``cryptography.x509.CertificateRevocationList`` - :return: ``None`` if the certificate revocation list was added - successfully. - """ - if isinstance(crl, x509.CertificateRevocationList): - from cryptography.hazmat.primitives.serialization import Encoding - - bio = _new_mem_buf(crl.public_bytes(Encoding.DER)) - openssl_crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) - _openssl_assert(openssl_crl != _ffi.NULL) - crl = _ffi.gc(openssl_crl, _lib.X509_CRL_free) - else: - raise TypeError( - "CRL must be of type " - "cryptography.x509.CertificateRevocationList" - ) - - _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl) != 0) - - def set_flags(self, flags: int) -> None: - """ - Set verification flags to this store. - - Verification flags can be combined by oring them together. - - .. note:: - - Setting a verification flag sometimes requires clients to add - additional information to the store, otherwise a suitable error will - be raised. - - For example, in setting flags to enable CRL checking a - suitable CRL must be added to the store otherwise an error will be - raised. - - .. versionadded:: 16.1.0 - - :param int flags: The verification flags to set on this store. - See :class:`X509StoreFlags` for available constants. - :return: ``None`` if the verification flags were successfully set. - """ - _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) - - def set_time(self, vfy_time: datetime.datetime) -> None: - """ - Set the time against which the certificates are verified. - - Normally the current time is used. - - .. note:: - - For example, you can determine if a certificate was valid at a given - time. - - .. versionadded:: 17.0.0 - - :param datetime vfy_time: The verification time to set on this store. - :return: ``None`` if the verification time was successfully set. - """ - param = _lib.X509_VERIFY_PARAM_new() - param = _ffi.gc(param, _lib.X509_VERIFY_PARAM_free) - - _lib.X509_VERIFY_PARAM_set_time( - param, calendar.timegm(vfy_time.timetuple()) - ) - _openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0) - - def load_locations( - self, - cafile: StrOrBytesPath | None, - capath: StrOrBytesPath | None = None, - ) -> None: - """ - Let X509Store know where we can find trusted certificates for the - certificate chain. Note that the certificates have to be in PEM - format. - - If *capath* is passed, it must be a directory prepared using the - ``c_rehash`` tool included with OpenSSL. Either, but not both, of - *cafile* or *capath* may be ``None``. - - .. note:: - - Both *cafile* and *capath* may be set simultaneously. - - Call this method multiple times to add more than one location. - For example, CA certificates, and certificate revocation list bundles - may be passed in *cafile* in subsequent calls to this method. - - .. versionadded:: 20.0 - - :param cafile: In which file we can find the certificates (``bytes`` or - ``unicode``). - :param capath: In which directory we can find the certificates - (``bytes`` or ``unicode``). - - :return: ``None`` if the locations were set successfully. - - :raises OpenSSL.crypto.Error: If both *cafile* and *capath* is ``None`` - or the locations could not be set for any reason. - - """ - if cafile is None: - cafile = _ffi.NULL - else: - cafile = _path_bytes(cafile) - - if capath is None: - capath = _ffi.NULL - else: - capath = _path_bytes(capath) - - load_result = _lib.X509_STORE_load_locations( - self._store, cafile, capath - ) - if not load_result: - _raise_current_error() - - -class X509StoreContextError(Exception): - """ - An exception raised when an error occurred while verifying a certificate - using `OpenSSL.X509StoreContext.verify_certificate`. - - :ivar certificate: The certificate which caused verificate failure. - :type certificate: :class:`X509` - """ - - def __init__( - self, message: str, errors: list[Any], certificate: X509 - ) -> None: - super().__init__(message) - self.errors = errors - self.certificate = certificate - - -class X509StoreContext: - """ - An X.509 store context. - - An X.509 store context is used to carry out the actual verification process - of a certificate in a described context. For describing such a context, see - :class:`X509Store`. - - :param X509Store store: The certificates which will be trusted for the - purposes of any verifications. - :param X509 certificate: The certificate to be verified. - :param chain: List of untrusted certificates that may be used for building - the certificate chain. May be ``None``. - :type chain: :class:`list` of :class:`X509` - """ - - def __init__( - self, - store: X509Store, - certificate: X509, - chain: Sequence[X509] | None = None, - ) -> None: - self._store = store - self._cert = certificate - self._chain = self._build_certificate_stack(chain) - - @staticmethod - def _build_certificate_stack( - certificates: Sequence[X509] | None, - ) -> None: - def cleanup(s: Any) -> None: - # Equivalent to sk_X509_pop_free, but we don't - # currently have a CFFI binding for that available - for i in range(_lib.sk_X509_num(s)): - x = _lib.sk_X509_value(s, i) - _lib.X509_free(x) - _lib.sk_X509_free(s) - - if certificates is None or len(certificates) == 0: - return _ffi.NULL - - stack = _lib.sk_X509_new_null() - _openssl_assert(stack != _ffi.NULL) - stack = _ffi.gc(stack, cleanup) - - for cert in certificates: - if not isinstance(cert, X509): - raise TypeError("One of the elements is not an X509 instance") - - _openssl_assert(_lib.X509_up_ref(cert._x509) > 0) - if _lib.sk_X509_push(stack, cert._x509) <= 0: - _lib.X509_free(cert._x509) - _raise_current_error() - - return stack - - @staticmethod - def _exception_from_context(store_ctx: Any) -> X509StoreContextError: - """ - Convert an OpenSSL native context error failure into a Python - exception. - - When a call to native OpenSSL X509_verify_cert fails, additional - information about the failure can be obtained from the store context. - """ - message = _ffi.string( - _lib.X509_verify_cert_error_string( - _lib.X509_STORE_CTX_get_error(store_ctx) - ) - ).decode("utf-8") - errors = [ - _lib.X509_STORE_CTX_get_error(store_ctx), - _lib.X509_STORE_CTX_get_error_depth(store_ctx), - message, - ] - # A context error should always be associated with a certificate, so we - # expect this call to never return :class:`None`. - _x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) - _cert = _lib.X509_dup(_x509) - pycert = X509._from_raw_x509_ptr(_cert) - return X509StoreContextError(message, errors, pycert) - - def _verify_certificate(self) -> Any: - """ - Verifies the certificate and runs an X509_STORE_CTX containing the - results. - - :raises X509StoreContextError: If an error occurred when validating a - certificate in the context. Sets ``certificate`` attribute to - indicate which certificate caused the error. - """ - store_ctx = _lib.X509_STORE_CTX_new() - _openssl_assert(store_ctx != _ffi.NULL) - store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free) - - ret = _lib.X509_STORE_CTX_init( - store_ctx, self._store._store, self._cert._x509, self._chain - ) - _openssl_assert(ret == 1) - - ret = _lib.X509_verify_cert(store_ctx) - if ret <= 0: - raise self._exception_from_context(store_ctx) - - return store_ctx - - def set_store(self, store: X509Store) -> None: - """ - Set the context's X.509 store. - - .. versionadded:: 0.15 - - :param X509Store store: The store description which will be used for - the purposes of any *future* verifications. - """ - self._store = store - - def verify_certificate(self) -> None: - """ - Verify a certificate in a context. - - .. versionadded:: 0.15 - - :raises X509StoreContextError: If an error occurred when validating a - certificate in the context. Sets ``certificate`` attribute to - indicate which certificate caused the error. - """ - self._verify_certificate() - - def get_verified_chain(self) -> list[X509]: - """ - Verify a certificate in a context and return the complete validated - chain. - - :raises X509StoreContextError: If an error occurred when validating a - certificate in the context. Sets ``certificate`` attribute to - indicate which certificate caused the error. - - .. versionadded:: 20.0 - """ - store_ctx = self._verify_certificate() - - # Note: X509_STORE_CTX_get1_chain returns a deep copy of the chain. - cert_stack = _lib.X509_STORE_CTX_get1_chain(store_ctx) - _openssl_assert(cert_stack != _ffi.NULL) - - result = [] - for i in range(_lib.sk_X509_num(cert_stack)): - cert = _lib.sk_X509_value(cert_stack, i) - _openssl_assert(cert != _ffi.NULL) - pycert = X509._from_raw_x509_ptr(cert) - result.append(pycert) - - # Free the stack but not the members which are freed by the X509 class. - _lib.sk_X509_free(cert_stack) - return result - - -def load_certificate(type: int, buffer: bytes) -> X509: - """ - Load a certificate (X509) from the string *buffer* encoded with the - type *type*. - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - - :param bytes buffer: The buffer the certificate is stored in - - :return: The X509 object - """ - if isinstance(buffer, str): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - x509 = _lib.d2i_X509_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if x509 == _ffi.NULL: - _raise_current_error() - - return X509._from_raw_x509_ptr(x509) - - -def dump_certificate(type: int, cert: X509) -> bytes: - """ - Dump the certificate *cert* into a buffer string encoded with the type - *type*. - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or - FILETYPE_TEXT) - :param cert: The certificate to dump - :return: The buffer with the dumped certificate in - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_X509(bio, cert._x509) - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_X509_bio(bio, cert._x509) - elif type == FILETYPE_TEXT: - result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT" - ) - - _openssl_assert(result_code == 1) - return _bio_to_string(bio) - - -def dump_publickey(type: int, pkey: PKey) -> bytes: - """ - Dump a public key to a buffer. - - :param type: The file type (one of :data:`FILETYPE_PEM` or - :data:`FILETYPE_ASN1`). - :param PKey pkey: The public key to dump - :return: The buffer with the dumped key in it. - :rtype: bytes - """ - bio = _new_mem_buf() - if type == FILETYPE_PEM: - write_bio = _lib.PEM_write_bio_PUBKEY - elif type == FILETYPE_ASN1: - write_bio = _lib.i2d_PUBKEY_bio - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - result_code = write_bio(bio, pkey._pkey) - if result_code != 1: # pragma: no cover - _raise_current_error() - - return _bio_to_string(bio) - - -def dump_privatekey( - type: int, - pkey: PKey, - cipher: str | None = None, - passphrase: PassphraseCallableT | None = None, -) -> bytes: - """ - Dump the private key *pkey* into a buffer string encoded with the type - *type*. Optionally (if *type* is :const:`FILETYPE_PEM`) encrypting it - using *cipher* and *passphrase*. - - :param type: The file type (one of :const:`FILETYPE_PEM`, - :const:`FILETYPE_ASN1`, or :const:`FILETYPE_TEXT`) - :param PKey pkey: The PKey to dump - :param cipher: (optional) if encrypted PEM format, the cipher to use - :param passphrase: (optional) if encrypted PEM format, this can be either - the passphrase to use, or a callback for providing the passphrase. - - :return: The buffer with the dumped key in - :rtype: bytes - - .. deprecated:: 26.3.0 - Use the serialization APIs on ``cryptography`` private key types - instead. - """ - bio = _new_mem_buf() - - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey") - - if cipher is not None: - if passphrase is None: - raise TypeError( - "if a value is given for cipher " - "one must also be given for passphrase" - ) - cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher)) - if cipher_obj == _ffi.NULL: - raise ValueError("Invalid cipher name") - else: - cipher_obj = _ffi.NULL - - helper = _PassphraseHelper(type, passphrase) - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_PrivateKey( - bio, - pkey._pkey, - cipher_obj, - _ffi.NULL, - 0, - helper.callback, - helper.callback_args, - ) - helper.raise_if_problem() - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey) - elif type == FILETYPE_TEXT: - if _lib.EVP_PKEY_id(pkey._pkey) != _lib.EVP_PKEY_RSA: - raise TypeError("Only RSA keys are supported for FILETYPE_TEXT") - - rsa = _ffi.gc(_lib.EVP_PKEY_get1_RSA(pkey._pkey), _lib.RSA_free) - result_code = _lib.RSA_print(bio, rsa, 0) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT" - ) - - _openssl_assert(result_code != 0) - - return _bio_to_string(bio) - - -_dump_privatekey_internal = dump_privatekey - -utils.deprecated( - dump_privatekey, - __name__, - ( - "dump_privatekey is deprecated. You should use the APIs in " - "cryptography." - ), - DeprecationWarning, - name="dump_privatekey", -) - - -class _PassphraseHelper: - def __init__( - self, - type: int, - passphrase: PassphraseCallableT | None, - more_args: bool = False, - truncate: bool = False, - ) -> None: - if type != FILETYPE_PEM and passphrase is not None: - raise ValueError( - "only FILETYPE_PEM key format supports encryption" - ) - self._passphrase = passphrase - self._more_args = more_args - self._truncate = truncate - self._problems: list[Exception] = [] - - @property - def callback(self) -> Any: - if self._passphrase is None: - return _ffi.NULL - elif isinstance(self._passphrase, bytes) or callable(self._passphrase): - return _ffi.callback("pem_password_cb", self._read_passphrase) - else: - raise TypeError( - "Last argument must be a byte string or a callable." - ) - - @property - def callback_args(self) -> Any: - if self._passphrase is None: - return _ffi.NULL - elif isinstance(self._passphrase, bytes) or callable(self._passphrase): - return _ffi.NULL - else: - raise TypeError( - "Last argument must be a byte string or a callable." - ) - - def raise_if_problem(self, exceptionType: type[Exception] = Error) -> None: - if self._problems: - # Flush the OpenSSL error queue - try: - _exception_from_error_queue(exceptionType) - except exceptionType: - pass - - raise self._problems.pop(0) - - def _read_passphrase( - self, buf: Any, size: int, rwflag: Any, userdata: Any - ) -> int: - try: - if callable(self._passphrase): - if self._more_args: - result = self._passphrase(size, rwflag, userdata) - else: - result = self._passphrase(rwflag) - else: - assert self._passphrase is not None - result = self._passphrase - if not isinstance(result, bytes): - raise ValueError("Bytes expected") - if len(result) > size: - if self._truncate: - result = result[:size] - else: - raise ValueError( - "passphrase returned by callback is too long" - ) - for i in range(len(result)): - buf[i] = result[i : i + 1] - return len(result) - except Exception as e: - self._problems.append(e) - return 0 - - -def load_publickey(type: int, buffer: str | bytes) -> PKey: - """ - Load a public key from a buffer. - - :param type: The file type (one of :data:`FILETYPE_PEM`, - :data:`FILETYPE_ASN1`). - :param buffer: The buffer the key is stored in. - :type buffer: A Python string object, either unicode or bytestring. - :return: The PKey object. - :rtype: :class:`PKey` - """ - if isinstance(buffer, str): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - evp_pkey = _lib.PEM_read_bio_PUBKEY( - bio, _ffi.NULL, _ffi.NULL, _ffi.NULL - ) - elif type == FILETYPE_ASN1: - evp_pkey = _lib.d2i_PUBKEY_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if evp_pkey == _ffi.NULL: - _raise_current_error() - - pkey = PKey.__new__(PKey) - pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - -def load_privatekey( - type: int, - buffer: str | bytes, - passphrase: PassphraseCallableT | None = None, -) -> PKey: - """ - Load a private key (PKey) from the string *buffer* encoded with the type - *type*. - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the key is stored in - :param passphrase: (optional) if encrypted PEM format, this can be - either the passphrase to use, or a callback for - providing the passphrase. - - :return: The PKey object - """ - if isinstance(buffer, str): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - helper = _PassphraseHelper(type, passphrase) - if type == FILETYPE_PEM: - evp_pkey = _lib.PEM_read_bio_PrivateKey( - bio, _ffi.NULL, helper.callback, helper.callback_args - ) - helper.raise_if_problem() - elif type == FILETYPE_ASN1: - evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if evp_pkey == _ffi.NULL: - _raise_current_error() - - pkey = PKey.__new__(PKey) - pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) - return pkey diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/debug.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/debug.py deleted file mode 100644 index e0ed3f8..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/debug.py +++ /dev/null @@ -1,40 +0,0 @@ -import ssl -import sys - -import cffi -import cryptography - -import OpenSSL.SSL - -from . import version - -_env_info = """\ -pyOpenSSL: {pyopenssl} -cryptography: {cryptography} -cffi: {cffi} -cryptography's compiled against OpenSSL: {crypto_openssl_compile} -cryptography's linked OpenSSL: {crypto_openssl_link} -Python's OpenSSL: {python_openssl} -Python executable: {python} -Python version: {python_version} -Platform: {platform} -sys.path: {sys_path}""".format( - pyopenssl=version.__version__, - crypto_openssl_compile=OpenSSL._util.ffi.string( - OpenSSL._util.lib.OPENSSL_VERSION_TEXT, - ).decode("ascii"), - crypto_openssl_link=OpenSSL.SSL.SSLeay_version( - OpenSSL.SSL.SSLEAY_VERSION - ).decode("ascii"), - python_openssl=getattr(ssl, "OPENSSL_VERSION", "n/a"), - cryptography=cryptography.__version__, - cffi=cffi.__version__, - python=sys.executable, - python_version=sys.version, - platform=sys.platform, - sys_path=sys.path, -) - - -if __name__ == "__main__": - print(_env_info) diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/py.typed b/tests/venv2/lib/python3.11/site-packages/OpenSSL/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/rand.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/rand.py deleted file mode 100644 index e57425f..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/rand.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -PRNG management routines, thin wrappers. -""" - -from __future__ import annotations - -import warnings - -from OpenSSL._util import lib as _lib - -warnings.warn( - "OpenSSL.rand is deprecated - you should use os.urandom instead", - DeprecationWarning, - stacklevel=3, -) - - -def add(buffer: bytes, entropy: int) -> None: - """ - Mix bytes from *string* into the PRNG state. - - The *entropy* argument is (the lower bound of) an estimate of how much - randomness is contained in *string*, measured in bytes. - - For more information, see e.g. :rfc:`1750`. - - This function is only relevant if you are forking Python processes and - need to reseed the CSPRNG after fork. - - :param buffer: Buffer with random data. - :param entropy: The entropy (in bytes) measurement of the buffer. - - :return: :obj:`None` - """ - if not isinstance(buffer, bytes): - raise TypeError("buffer must be a byte string") - - if not isinstance(entropy, int): - raise TypeError("entropy must be an integer") - - _lib.RAND_add(buffer, len(buffer), entropy) - - -def status() -> int: - """ - Check whether the PRNG has been seeded with enough data. - - :return: 1 if the PRNG is seeded enough, 0 otherwise. - """ - return _lib.RAND_status() diff --git a/tests/venv2/lib/python3.11/site-packages/OpenSSL/version.py b/tests/venv2/lib/python3.11/site-packages/OpenSSL/version.py deleted file mode 100644 index 43aea61..0000000 --- a/tests/venv2/lib/python3.11/site-packages/OpenSSL/version.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) AB Strakt -# Copyright (C) Jean-Paul Calderone -# See LICENSE for details. - -""" -pyOpenSSL - A simple wrapper around the OpenSSL library -""" - -__all__ = [ - "__author__", - "__copyright__", - "__email__", - "__license__", - "__summary__", - "__title__", - "__uri__", - "__version__", -] - -__version__ = "26.3.0" - -__title__ = "pyOpenSSL" -__uri__ = "https://pyopenssl.org/" -__summary__ = "Python wrapper module around the OpenSSL library" -__author__ = "The pyOpenSSL developers" -__email__ = "cryptography-dev@python.org" -__license__ = "Apache License, Version 2.0" -__copyright__ = f"Copyright 2001-2026 {__author__}" diff --git a/tests/venv2/lib/python3.11/site-packages/__pycache__/py.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/__pycache__/py.cpython-311.pyc deleted file mode 100644 index a78d3ba..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/__pycache__/py.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc deleted file mode 100644 index cc46d6c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_cffi_backend.cpython-311-x86_64-linux-gnu.so b/tests/venv2/lib/python3.11/site-packages/_cffi_backend.cpython-311-x86_64-linux-gnu.so deleted file mode 100755 index 473e4f5..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_cffi_backend.cpython-311-x86_64-linux-gnu.so and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__init__.py b/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__init__.py deleted file mode 100644 index f987a53..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__init__.py +++ /dev/null @@ -1,222 +0,0 @@ -# don't import any costly modules -import sys -import os - - -is_pypy = '__pypy__' in sys.builtin_module_names - - -def warn_distutils_present(): - if 'distutils' not in sys.modules: - return - if is_pypy and sys.version_info < (3, 7): - # PyPy for 3.6 unconditionally imports distutils, so bypass the warning - # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 - return - import warnings - - warnings.warn( - "Distutils was imported before Setuptools, but importing Setuptools " - "also replaces the `distutils` module in `sys.modules`. This may lead " - "to undesirable behaviors or errors. To avoid these issues, avoid " - "using distutils directly, ensure that setuptools is installed in the " - "traditional way (e.g. not an editable install), and/or make sure " - "that setuptools is always imported before distutils." - ) - - -def clear_distutils(): - if 'distutils' not in sys.modules: - return - import warnings - - warnings.warn("Setuptools is replacing distutils.") - mods = [ - name - for name in sys.modules - if name == "distutils" or name.startswith("distutils.") - ] - for name in mods: - del sys.modules[name] - - -def enabled(): - """ - Allow selection of distutils by environment variable. - """ - which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local') - return which == 'local' - - -def ensure_local_distutils(): - import importlib - - clear_distutils() - - # With the DistutilsMetaFinder in place, - # perform an import to cause distutils to be - # loaded from setuptools._distutils. Ref #2906. - with shim(): - importlib.import_module('distutils') - - # check that submodules load as expected - core = importlib.import_module('distutils.core') - assert '_distutils' in core.__file__, core.__file__ - assert 'setuptools._distutils.log' not in sys.modules - - -def do_override(): - """ - Ensure that the local copy of distutils is preferred over stdlib. - - See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 - for more motivation. - """ - if enabled(): - warn_distutils_present() - ensure_local_distutils() - - -class _TrivialRe: - def __init__(self, *patterns): - self._patterns = patterns - - def match(self, string): - return all(pat in string for pat in self._patterns) - - -class DistutilsMetaFinder: - def find_spec(self, fullname, path, target=None): - # optimization: only consider top level modules and those - # found in the CPython test suite. - if path is not None and not fullname.startswith('test.'): - return - - method_name = 'spec_for_{fullname}'.format(**locals()) - method = getattr(self, method_name, lambda: None) - return method() - - def spec_for_distutils(self): - if self.is_cpython(): - return - - import importlib - import importlib.abc - import importlib.util - - try: - mod = importlib.import_module('setuptools._distutils') - except Exception: - # There are a couple of cases where setuptools._distutils - # may not be present: - # - An older Setuptools without a local distutils is - # taking precedence. Ref #2957. - # - Path manipulation during sitecustomize removes - # setuptools from the path but only after the hook - # has been loaded. Ref #2980. - # In either case, fall back to stdlib behavior. - return - - class DistutilsLoader(importlib.abc.Loader): - def create_module(self, spec): - mod.__name__ = 'distutils' - return mod - - def exec_module(self, module): - pass - - return importlib.util.spec_from_loader( - 'distutils', DistutilsLoader(), origin=mod.__file__ - ) - - @staticmethod - def is_cpython(): - """ - Suppress supplying distutils for CPython (build and tests). - Ref #2965 and #3007. - """ - return os.path.isfile('pybuilddir.txt') - - def spec_for_pip(self): - """ - Ensure stdlib distutils when running under pip. - See pypa/pip#8761 for rationale. - """ - if self.pip_imported_during_build(): - return - clear_distutils() - self.spec_for_distutils = lambda: None - - @classmethod - def pip_imported_during_build(cls): - """ - Detect if pip is being imported in a build script. Ref #2355. - """ - import traceback - - return any( - cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None) - ) - - @staticmethod - def frame_file_is_setup(frame): - """ - Return True if the indicated frame suggests a setup.py file. - """ - # some frames may not have __file__ (#2940) - return frame.f_globals.get('__file__', '').endswith('setup.py') - - def spec_for_sensitive_tests(self): - """ - Ensure stdlib distutils when running select tests under CPython. - - python/cpython#91169 - """ - clear_distutils() - self.spec_for_distutils = lambda: None - - sensitive_tests = ( - [ - 'test.test_distutils', - 'test.test_peg_generator', - 'test.test_importlib', - ] - if sys.version_info < (3, 10) - else [ - 'test.test_distutils', - ] - ) - - -for name in DistutilsMetaFinder.sensitive_tests: - setattr( - DistutilsMetaFinder, - f'spec_for_{name}', - DistutilsMetaFinder.spec_for_sensitive_tests, - ) - - -DISTUTILS_FINDER = DistutilsMetaFinder() - - -def add_shim(): - DISTUTILS_FINDER in sys.meta_path or insert_shim() - - -class shim: - def __enter__(self): - insert_shim() - - def __exit__(self, exc, value, tb): - remove_shim() - - -def insert_shim(): - sys.meta_path.insert(0, DISTUTILS_FINDER) - - -def remove_shim(): - try: - sys.meta_path.remove(DISTUTILS_FINDER) - except ValueError: - pass diff --git a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 3622cd3..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc deleted file mode 100644 index cede0e9..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/override.py b/tests/venv2/lib/python3.11/site-packages/_distutils_hack/override.py deleted file mode 100644 index 2cc433a..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_distutils_hack/override.py +++ /dev/null @@ -1 +0,0 @@ -__import__('_distutils_hack').do_override() diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/__init__.py deleted file mode 100644 index 8eb8ec9..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import annotations - - -__all__ = ["__version__", "version_tuple"] - -try: - from ._version import version as __version__ - from ._version import version_tuple -except ImportError: # pragma: no cover - # broken installation, we don't even try - # unknown only works because we do poor mans version compare - __version__ = "unknown" - version_tuple = (0, 0, "unknown") diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 1579ea4..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/_argcomplete.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/_argcomplete.cpython-311.pyc deleted file mode 100644 index c648204..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/_argcomplete.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/_version.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/_version.cpython-311.pyc deleted file mode 100644 index d6ad822..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/_version.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/cacheprovider.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/cacheprovider.cpython-311.pyc deleted file mode 100644 index e861777..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/cacheprovider.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/capture.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/capture.cpython-311.pyc deleted file mode 100644 index fc00807..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/capture.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/compat.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/compat.cpython-311.pyc deleted file mode 100644 index 7c6e201..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/compat.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/debugging.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/debugging.cpython-311.pyc deleted file mode 100644 index 0a8cccf..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/debugging.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/deprecated.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/deprecated.cpython-311.pyc deleted file mode 100644 index ae47756..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/deprecated.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/doctest.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/doctest.cpython-311.pyc deleted file mode 100644 index 0f48d61..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/doctest.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/faulthandler.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/faulthandler.cpython-311.pyc deleted file mode 100644 index 05f1d48..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/faulthandler.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/fixtures.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/fixtures.cpython-311.pyc deleted file mode 100644 index 5f5503d..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/fixtures.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/freeze_support.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/freeze_support.cpython-311.pyc deleted file mode 100644 index 89bf7da..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/freeze_support.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/helpconfig.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/helpconfig.cpython-311.pyc deleted file mode 100644 index c3419fe..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/helpconfig.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/hookspec.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/hookspec.cpython-311.pyc deleted file mode 100644 index 68d3cb7..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/hookspec.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/junitxml.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/junitxml.cpython-311.pyc deleted file mode 100644 index 77862a7..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/junitxml.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/legacypath.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/legacypath.cpython-311.pyc deleted file mode 100644 index 7503589..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/legacypath.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/logging.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/logging.cpython-311.pyc deleted file mode 100644 index 65f116d..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/logging.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/main.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/main.cpython-311.pyc deleted file mode 100644 index 64d4794..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/main.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/monkeypatch.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/monkeypatch.cpython-311.pyc deleted file mode 100644 index 37461db..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/monkeypatch.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/nodes.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/nodes.cpython-311.pyc deleted file mode 100644 index e434b4f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/nodes.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/outcomes.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/outcomes.cpython-311.pyc deleted file mode 100644 index 6a9acb9..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/outcomes.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pastebin.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pastebin.cpython-311.pyc deleted file mode 100644 index 19ef0e9..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pastebin.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pathlib.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pathlib.cpython-311.pyc deleted file mode 100644 index cbfbc52..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pathlib.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pytester.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pytester.cpython-311.pyc deleted file mode 100644 index fc16ab6..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pytester.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pytester_assertions.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pytester_assertions.cpython-311.pyc deleted file mode 100644 index 5445964..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/pytester_assertions.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/python.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/python.cpython-311.pyc deleted file mode 100644 index 56181e2..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/python.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/python_api.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/python_api.cpython-311.pyc deleted file mode 100644 index 2204895..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/python_api.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/raises.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/raises.cpython-311.pyc deleted file mode 100644 index 5354e7b..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/raises.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/recwarn.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/recwarn.cpython-311.pyc deleted file mode 100644 index ed7a78b..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/recwarn.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/reports.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/reports.cpython-311.pyc deleted file mode 100644 index 28aea28..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/reports.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/runner.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/runner.cpython-311.pyc deleted file mode 100644 index 281bf5f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/runner.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/scope.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/scope.cpython-311.pyc deleted file mode 100644 index 0f28b75..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/scope.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/setuponly.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/setuponly.cpython-311.pyc deleted file mode 100644 index 91ee540..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/setuponly.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/setupplan.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/setupplan.cpython-311.pyc deleted file mode 100644 index d1c3485..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/setupplan.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/skipping.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/skipping.cpython-311.pyc deleted file mode 100644 index 7921421..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/skipping.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/stash.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/stash.cpython-311.pyc deleted file mode 100644 index 0129072..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/stash.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/stepwise.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/stepwise.cpython-311.pyc deleted file mode 100644 index b505bad..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/stepwise.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/subtests.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/subtests.cpython-311.pyc deleted file mode 100644 index 46cefd6..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/subtests.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/terminal.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/terminal.cpython-311.pyc deleted file mode 100644 index d2614f3..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/terminal.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/terminalprogress.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/terminalprogress.cpython-311.pyc deleted file mode 100644 index 27dc0d8..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/terminalprogress.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/threadexception.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/threadexception.cpython-311.pyc deleted file mode 100644 index 526fc43..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/threadexception.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/timing.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/timing.cpython-311.pyc deleted file mode 100644 index 346c272..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/timing.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/tmpdir.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/tmpdir.cpython-311.pyc deleted file mode 100644 index 3a1aa2c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/tmpdir.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/tracemalloc.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/tracemalloc.cpython-311.pyc deleted file mode 100644 index b081945..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/tracemalloc.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/unittest.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/unittest.cpython-311.pyc deleted file mode 100644 index 583fb79..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/unittest.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/unraisableexception.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/unraisableexception.cpython-311.pyc deleted file mode 100644 index 1d7db8c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/unraisableexception.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/warning_types.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/warning_types.cpython-311.pyc deleted file mode 100644 index 0fce05f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/warning_types.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/warnings.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/warnings.cpython-311.pyc deleted file mode 100644 index f07f274..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/__pycache__/warnings.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_argcomplete.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_argcomplete.py deleted file mode 100644 index 59426ef..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_argcomplete.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Allow bash-completion for argparse with argcomplete if installed. - -Needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail -to find the magic string, so _ARGCOMPLETE env. var is never set, and -this does not need special code). - -Function try_argcomplete(parser) should be called directly before -the call to ArgumentParser.parse_args(). - -The filescompleter is what you normally would use on the positional -arguments specification, in order to get "dirname/" after "dirn" -instead of the default "dirname ": - - optparser.add_argument(Config._file_or_dir, nargs='*').completer=filescompleter - -Other, application specific, completers should go in the file -doing the add_argument calls as they need to be specified as .completer -attributes as well. (If argcomplete is not installed, the function the -attribute points to will not be used). - -SPEEDUP -======= - -The generic argcomplete script for bash-completion -(/etc/bash_completion.d/python-argcomplete.sh) -uses a python program to determine startup script generated by pip. -You can speed up completion somewhat by changing this script to include - # PYTHON_ARGCOMPLETE_OK -so the python-argcomplete-check-easy-install-script does not -need to be called to find the entry point of the code and see if that is -marked with PYTHON_ARGCOMPLETE_OK. - -INSTALL/DEBUGGING -================= - -To include this support in another application that has setup.py generated -scripts: - -- Add the line: - # PYTHON_ARGCOMPLETE_OK - near the top of the main python entry point. - -- Include in the file calling parse_args(): - from _argcomplete import try_argcomplete, filescompleter - Call try_argcomplete just before parse_args(), and optionally add - filescompleter to the positional arguments' add_argument(). - -If things do not work right away: - -- Switch on argcomplete debugging with (also helpful when doing custom - completers): - export _ARC_DEBUG=1 - -- Run: - python-argcomplete-check-easy-install-script $(which appname) - echo $? - will echo 0 if the magic line has been found, 1 if not. - -- Sometimes it helps to find early on errors using: - _ARGCOMPLETE=1 _ARC_DEBUG=1 appname - which should throw a KeyError: 'COMPLINE' (which is properly set by the - global argcomplete script). -""" - -from __future__ import annotations - -import argparse -from glob import glob -import os -import sys -from typing import Any - - -class FastFilesCompleter: - """Fast file completer class.""" - - def __init__(self, directories: bool = True) -> None: - self.directories = directories - - def __call__(self, prefix: str, **kwargs: Any) -> list[str]: - # Only called on non option completions. - if os.sep in prefix[1:]: - prefix_dir = len(os.path.dirname(prefix) + os.sep) - else: - prefix_dir = 0 - completion = [] - globbed = [] - if "*" not in prefix and "?" not in prefix: - # We are on unix, otherwise no bash. - if not prefix or prefix[-1] == os.sep: - globbed.extend(glob(prefix + ".*")) - prefix += "*" - globbed.extend(glob(prefix)) - for x in sorted(globbed): - if os.path.isdir(x): - x += "/" - # Append stripping the prefix (like bash, not like compgen). - completion.append(x[prefix_dir:]) - return completion - - -if os.environ.get("_ARGCOMPLETE"): - try: - import argcomplete.completers - except ImportError: - sys.exit(-1) - filescompleter: FastFilesCompleter | None = FastFilesCompleter() - - def try_argcomplete(parser: argparse.ArgumentParser) -> None: - argcomplete.autocomplete(parser, always_complete_options=False) - -else: - - def try_argcomplete(parser: argparse.ArgumentParser) -> None: - pass - - filescompleter = None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__init__.py deleted file mode 100644 index 7f67a2e..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Python inspection/code generation API.""" - -from __future__ import annotations - -from .code import Code -from .code import ExceptionInfo -from .code import filter_traceback -from .code import Frame -from .code import getfslineno -from .code import Traceback -from .code import TracebackEntry -from .source import getrawcode -from .source import Source - - -__all__ = [ - "Code", - "ExceptionInfo", - "Frame", - "Source", - "Traceback", - "TracebackEntry", - "filter_traceback", - "getfslineno", - "getrawcode", -] diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6978670..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/code.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/code.cpython-311.pyc deleted file mode 100644 index a1c95a6..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/code.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/source.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/source.cpython-311.pyc deleted file mode 100644 index c1c6210..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/__pycache__/source.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/code.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_code/code.py deleted file mode 100644 index 3c453b1..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/code.py +++ /dev/null @@ -1,1632 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import ast -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Mapping -from collections.abc import Sequence -import dataclasses -import inspect -from io import StringIO -import os -from pathlib import Path -import re -import sys -from traceback import extract_tb -from traceback import format_exception -from traceback import format_exception_only -from traceback import FrameSummary -from types import CodeType -from types import FrameType -from types import TracebackType -from typing import Any -from typing import ClassVar -from typing import Final -from typing import final -from typing import Generic -from typing import Literal -from typing import overload -from typing import SupportsIndex -from typing import TypeAlias -from typing import TypeVar - -import pluggy - -import _pytest -from _pytest._code.source import findsource -from _pytest._code.source import getrawcode -from _pytest._code.source import getstatementrange_ast -from _pytest._code.source import Source -from _pytest._io import TerminalWriter -from _pytest._io.saferepr import safeformat -from _pytest._io.saferepr import saferepr -from _pytest.compat import get_real_func -from _pytest.deprecated import check_ispytest -from _pytest.pathlib import absolutepath -from _pytest.pathlib import bestrelpath - - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - -TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] - -EXCEPTION_OR_MORE = type[BaseException] | tuple[type[BaseException], ...] - - -class Code: - """Wrapper around Python code objects.""" - - __slots__ = ("raw",) - - def __init__(self, obj: CodeType) -> None: - self.raw = obj - - @classmethod - def from_function(cls, obj: object) -> Code: - return cls(getrawcode(obj)) - - def __eq__(self, other): - return self.raw == other.raw - - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore - - @property - def firstlineno(self) -> int: - return self.raw.co_firstlineno - 1 - - @property - def name(self) -> str: - return self.raw.co_name - - @property - def path(self) -> Path | str: - """Return a path object pointing to source code, or an ``str`` in - case of ``OSError`` / non-existing file.""" - filename = inspect.getfile(self.raw) - if not filename: - return "" - try: - p = absolutepath(filename) - # maybe don't try this checking - if not p.exists(): - raise OSError("path check failed.") - return p - except OSError: - # XXX maybe try harder like the weird logic - # in the standard lib [linecache.updatecache] does? - return filename - - @property - def fullsource(self) -> Source | None: - """Return a _pytest._code.Source object for the full source file of the code.""" - full, _ = findsource(self.raw) - return full - - def source(self) -> Source: - """Return a _pytest._code.Source object for the code object's source only.""" - # return source only for that part of code - return Source(self.raw) - - def getargs(self, var: bool = False) -> tuple[str, ...]: - """Return a tuple with the argument names for the code object. - - If 'var' is set True also return the names of the variable and - keyword arguments when present. - """ - # inspect.getargs merges positional and kwonly into a single list; - # co_argcount is needed to exclude kwonly when var=False. - args, varargs, varkw = inspect.getargs(self.raw) - if not var: - return tuple(args[: self.raw.co_argcount]) - result = list(args) - if varargs is not None: - result.append(varargs) - if varkw is not None: - result.append(varkw) - return tuple(result) - - -class Frame: - """Wrapper around a Python frame holding f_locals and f_globals - in which expressions can be evaluated.""" - - __slots__ = ("raw",) - - def __init__(self, frame: FrameType) -> None: - self.raw = frame - - @property - def lineno(self) -> int: - return self.raw.f_lineno - 1 - - @property - def f_globals(self) -> dict[str, Any]: - return self.raw.f_globals - - @property - def f_locals(self) -> dict[str, Any]: - return self.raw.f_locals - - @property - def code(self) -> Code: - return Code(self.raw.f_code) - - @property - def statement(self) -> Source: - """Statement this frame is at.""" - if self.code.fullsource is None: - return Source("") - return self.code.fullsource.getstatement(self.lineno) - - def eval(self, code, **vars): - """Evaluate 'code' in the frame. - - 'vars' are optional additional local variables. - - Returns the result of the evaluation. - """ - f_locals = self.f_locals.copy() - f_locals.update(vars) - return eval(code, self.f_globals, f_locals) - - def repr(self, object: object) -> str: - """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" - return saferepr(object) - - def getargs(self, var: bool = False): - """Return a list of tuples (name, value) for all arguments. - - If 'var' is set True, also include the variable and keyword arguments - when present. - """ - retval = [] - for arg in self.code.getargs(var): - try: - retval.append((arg, self.f_locals[arg])) - except KeyError: - pass # this can occur when using Psyco - return retval - - -class TracebackEntry: - """A single entry in a Traceback.""" - - __slots__ = ("_rawentry", "_repr_style") - - def __init__( - self, - rawentry: TracebackType, - repr_style: Literal["short", "long"] | None = None, - ) -> None: - self._rawentry: Final = rawentry - self._repr_style: Final = repr_style - - def with_repr_style( - self, repr_style: Literal["short", "long"] | None - ) -> TracebackEntry: - return TracebackEntry(self._rawentry, repr_style) - - @property - def lineno(self) -> int: - return self._rawentry.tb_lineno - 1 - - def get_python_framesummary(self) -> FrameSummary: - # Python's built-in traceback module implements all the nitty gritty - # details to get column numbers of out frames. - stack_summary = extract_tb(self._rawentry, limit=1) - return stack_summary[0] - - # Column and end line numbers introduced in python 3.11 - if sys.version_info < (3, 11): - - @property - def end_lineno_relative(self) -> int | None: - return None - - @property - def colno(self) -> int | None: - return None - - @property - def end_colno(self) -> int | None: - return None - else: - - @property - def end_lineno_relative(self) -> int | None: - frame_summary = self.get_python_framesummary() - if frame_summary.end_lineno is None: # pragma: no cover - return None - return frame_summary.end_lineno - 1 - self.frame.code.firstlineno - - @property - def colno(self) -> int | None: - """Starting byte offset of the expression in the traceback entry.""" - return self.get_python_framesummary().colno - - @property - def end_colno(self) -> int | None: - """Ending byte offset of the expression in the traceback entry.""" - return self.get_python_framesummary().end_colno - - @property - def frame(self) -> Frame: - return Frame(self._rawentry.tb_frame) - - @property - def relline(self) -> int: - return self.lineno - self.frame.code.firstlineno - - def __repr__(self) -> str: - return f"" - - @property - def statement(self) -> Source: - """_pytest._code.Source object for the current statement.""" - source = self.frame.code.fullsource - assert source is not None - return source.getstatement(self.lineno) - - @property - def path(self) -> Path | str: - """Path to the source code.""" - return self.frame.code.path - - @property - def locals(self) -> dict[str, Any]: - """Locals of underlying frame.""" - return self.frame.f_locals - - def getfirstlinesource(self) -> int: - return self.frame.code.firstlineno - - def getsource( - self, astcache: dict[str | Path, ast.AST] | None = None - ) -> Source | None: - """Return failing source code.""" - # we use the passed in astcache to not reparse asttrees - # within exception info printing - source = self.frame.code.fullsource - if source is None: - return None - key = astnode = None - if astcache is not None: - key = self.frame.code.path - if key is not None: - astnode = astcache.get(key, None) - start = self.getfirstlinesource() - try: - astnode, _, end = getstatementrange_ast( - self.lineno, source, astnode=astnode - ) - except SyntaxError: - end = self.lineno + 1 - else: - if key is not None and astcache is not None: - astcache[key] = astnode - return source[start:end] - - source = property(getsource) - - def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: - """Return True if the current frame has a var __tracebackhide__ - resolving to True. - - If __tracebackhide__ is a callable, it gets called with the - ExceptionInfo instance and can decide whether to hide the traceback. - - Mostly for internal use. - """ - tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False - for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): - # in normal cases, f_locals and f_globals are dictionaries - # however via `exec(...)` / `eval(...)` they can be other types - # (even incorrect types!). - # as such, we suppress all exceptions while accessing __tracebackhide__ - try: - tbh = maybe_ns_dct["__tracebackhide__"] - except Exception: - pass - else: - break - if tbh and callable(tbh): - return tbh(excinfo) - return tbh - - def __str__(self) -> str: - name = self.frame.code.name - try: - line = str(self.statement).lstrip() - except KeyboardInterrupt: - raise - except BaseException: - line = "???" - # This output does not quite match Python's repr for traceback entries, - # but changing it to do so would break certain plugins. See - # https://github.com/pytest-dev/pytest/pull/7535/ for details. - return f" File '{self.path}':{self.lineno + 1} in {name}\n {line}\n" - - @property - def name(self) -> str: - """co_name of underlying code.""" - return self.frame.code.raw.co_name - - -class Traceback(list[TracebackEntry]): - """Traceback objects encapsulate and offer higher level access to Traceback entries.""" - - def __init__( - self, - tb: TracebackType | Iterable[TracebackEntry], - ) -> None: - """Initialize from given python traceback object and ExceptionInfo.""" - if isinstance(tb, TracebackType): - - def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: TracebackType | None = cur - while cur_ is not None: - yield TracebackEntry(cur_) - cur_ = cur_.tb_next - - super().__init__(f(tb)) - else: - super().__init__(tb) - - def cut( - self, - path: os.PathLike[str] | str | None = None, - lineno: int | None = None, - firstlineno: int | None = None, - excludepath: os.PathLike[str] | None = None, - ) -> Traceback: - """Return a Traceback instance wrapping part of this Traceback. - - By providing any combination of path, lineno and firstlineno, the - first frame to start the to-be-returned traceback is determined. - - This allows cutting the first part of a Traceback instance e.g. - for formatting reasons (removing some uninteresting bits that deal - with handling of the exception/traceback). - """ - path_ = None if path is None else os.fspath(path) - excludepath_ = None if excludepath is None else os.fspath(excludepath) - for x in self: - code = x.frame.code - codepath = code.path - if path is not None and str(codepath) != path_: - continue - if ( - excludepath is not None - and isinstance(codepath, Path) - and excludepath_ in (str(p) for p in codepath.parents) # type: ignore[operator] - ): - continue - if lineno is not None and x.lineno != lineno: - continue - if firstlineno is not None and x.frame.code.firstlineno != firstlineno: - continue - return Traceback(x._rawentry) - return self - - @overload - def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... - - @overload - def __getitem__(self, key: slice) -> Traceback: ... - - def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: - if isinstance(key, slice): - return self.__class__(super().__getitem__(key)) - else: - return super().__getitem__(key) - - def filter( - self, - excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool], - /, - ) -> Traceback: - """Return a Traceback instance with certain items removed. - - If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s - which are hidden (see ishidden() above). - - Otherwise, the filter is a function that gets a single argument, a - ``TracebackEntry`` instance, and should return True when the item should - be added to the ``Traceback``, False when not. - """ - if isinstance(excinfo_or_fn, ExceptionInfo): - fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731 - else: - fn = excinfo_or_fn - return Traceback(filter(fn, self)) - - def recursionindex(self) -> int | None: - """Return the index of the frame/TracebackEntry where recursion originates if - appropriate, None if no recursion occurred.""" - cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} - for i, entry in enumerate(self): - # id for the code.raw is needed to work around - # the strange metaprogramming in the decorator lib from pypi - # which generates code objects that have hash/value equality - # XXX needs a test - key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno - values = cache.setdefault(key, []) - # Since Python 3.13 f_locals is a proxy, freeze it. - loc = dict(entry.frame.f_locals) - if values: - for otherloc in values: - if otherloc == loc: - return i - values.append(loc) - return None - - -def stringify_exception( - exc: BaseException, include_subexception_msg: bool = True -) -> str: - try: - notes = getattr(exc, "__notes__", []) - except KeyError: - # Workaround for https://github.com/python/cpython/issues/98778 on - # some 3.10 and 3.11 patch versions. - HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) - if sys.version_info < (3, 12) and isinstance(exc, HTTPError): - notes = [] - else: # pragma: no cover - # exception not related to above bug, reraise - raise - if not include_subexception_msg and isinstance(exc, BaseExceptionGroup): - message = exc.message - else: - message = str(exc) - - return "\n".join( - [ - message, - *notes, - ] - ) - - -E = TypeVar("E", bound=BaseException, covariant=True) - - -@final -@dataclasses.dataclass -class ExceptionInfo(Generic[E]): - """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" - - _assert_start_repr: ClassVar = "AssertionError('assert " - - _excinfo: tuple[type[E], E, TracebackType] | None - _striptext: str - _traceback: Traceback | None - - def __init__( - self, - excinfo: tuple[type[E], E, TracebackType] | None, - striptext: str = "", - traceback: Traceback | None = None, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self._excinfo = excinfo - self._striptext = striptext - self._traceback = traceback - - @classmethod - def from_exception( - cls, - # Ignoring error: "Cannot use a covariant type variable as a parameter". - # This is OK to ignore because this class is (conceptually) readonly. - # See https://github.com/python/mypy/issues/7049. - exception: E, # type: ignore[misc] - exprinfo: str | None = None, - ) -> ExceptionInfo[E]: - """Return an ExceptionInfo for an existing exception. - - The exception must have a non-``None`` ``__traceback__`` attribute, - otherwise this function fails with an assertion error. This means that - the exception must have been raised, or added a traceback with the - :py:meth:`~BaseException.with_traceback()` method. - - :param exprinfo: - A text string helping to determine if we should strip - ``AssertionError`` from the output. Defaults to the exception - message/``__str__()``. - - .. versionadded:: 7.4 - """ - assert exception.__traceback__, ( - "Exceptions passed to ExcInfo.from_exception(...)" - " must have a non-None __traceback__." - ) - exc_info = (type(exception), exception, exception.__traceback__) - return cls.from_exc_info(exc_info, exprinfo) - - @classmethod - def from_exc_info( - cls, - exc_info: tuple[type[E], E, TracebackType], - exprinfo: str | None = None, - ) -> ExceptionInfo[E]: - """Like :func:`from_exception`, but using old-style exc_info tuple.""" - _striptext = "" - if exprinfo is None and isinstance(exc_info[1], AssertionError): - exprinfo = getattr(exc_info[1], "msg", None) - if exprinfo is None: - exprinfo = saferepr(exc_info[1]) - if exprinfo and exprinfo.startswith(cls._assert_start_repr): - _striptext = "AssertionError: " - - return cls(exc_info, _striptext, _ispytest=True) - - @classmethod - def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: - """Return an ExceptionInfo matching the current traceback. - - .. warning:: - - Experimental API - - :param exprinfo: - A text string helping to determine if we should strip - ``AssertionError`` from the output. Defaults to the exception - message/``__str__()``. - """ - tup = sys.exc_info() - assert tup[0] is not None, "no current exception" - assert tup[1] is not None, "no current exception" - assert tup[2] is not None, "no current exception" - exc_info = (tup[0], tup[1], tup[2]) - return ExceptionInfo.from_exc_info(exc_info, exprinfo) - - @classmethod - def for_later(cls) -> ExceptionInfo[E]: - """Return an unfilled ExceptionInfo.""" - return cls(None, _ispytest=True) - - def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: - """Fill an unfilled ExceptionInfo created with ``for_later()``.""" - assert self._excinfo is None, "ExceptionInfo was already filled" - self._excinfo = exc_info - - @property - def type(self) -> type[E]: - """The exception class.""" - assert self._excinfo is not None, ( - ".type can only be used after the context manager exits" - ) - return self._excinfo[0] - - @property - def value(self) -> E: - """The exception value.""" - assert self._excinfo is not None, ( - ".value can only be used after the context manager exits" - ) - return self._excinfo[1] - - @property - def tb(self) -> TracebackType: - """The exception raw traceback.""" - assert self._excinfo is not None, ( - ".tb can only be used after the context manager exits" - ) - return self._excinfo[2] - - @property - def typename(self) -> str: - """The type name of the exception.""" - assert self._excinfo is not None, ( - ".typename can only be used after the context manager exits" - ) - return self.type.__name__ - - @property - def traceback(self) -> Traceback: - """The traceback.""" - if self._traceback is None: - self._traceback = Traceback(self.tb) - return self._traceback - - @traceback.setter - def traceback(self, value: Traceback) -> None: - self._traceback = value - - def __repr__(self) -> str: - if self._excinfo is None: - return "" - return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>" - - def exconly(self, tryshort: bool = False) -> str: - """Return the exception as a string. - - This is usually a single line ": ", but - may also include additional lines for the exception notes, and detailed - information for SyntaxError's. - - :param tryshort: - If true, and the exception is an AssertionError, strip - 'AssertionError: ' from the beginning. - """ - - def _get_single_subexc( - eg: BaseExceptionGroup[BaseException], - ) -> BaseException | None: - if len(eg.exceptions) != 1: - return None - if isinstance(e := eg.exceptions[0], BaseExceptionGroup): - return _get_single_subexc(e) - return e - - if ( - tryshort - and isinstance(self.value, BaseExceptionGroup) - and (subexc := _get_single_subexc(self.value)) is not None - ): - return f"{subexc!r} [single exception in {type(self.value).__name__}]" - - lines = format_exception_only(self.value) - # The lines already include line separators. - text = "".join(lines) - text = text.rstrip() - if tryshort: - if text.startswith(self._striptext): - text = text[len(self._striptext) :] - return text - - def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: - """Return True if the exception is an instance of exc. - - Consider using ``isinstance(excinfo.value, exc)`` instead. - """ - return isinstance(self.value, exc) - - def _getreprcrash(self) -> ReprFileLocation | None: - # Find last non-hidden traceback entry that led to the exception of the - # traceback, or None if all hidden. - for i in range(-1, -len(self.traceback) - 1, -1): - entry = self.traceback[i] - if not entry.ishidden(self): - path, lineno = entry.frame.code.raw.co_filename, entry.lineno - exconly = self.exconly(tryshort=True) - return ReprFileLocation(path, lineno + 1, exconly) - return None - - def getrepr( - self, - showlocals: bool = False, - style: TracebackStyle = "long", - abspath: bool = False, - tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, - funcargs: bool = False, - truncate_locals: bool = True, - truncate_args: bool = True, - chain: bool = True, - ) -> ReprExceptionInfo | ExceptionChainRepr: - """Return str()able representation of this exception info. - - The formatting parameters are ineffective if ``style="native"``, - since in this case the native formatting is used. - - :param bool showlocals: - Show locals per traceback entry. - - :param str style: - long|short|line|no|native|value traceback style. - - :param bool abspath: - If paths should be changed to absolute or left unchanged. - - :param tbfilter: - A filter for traceback entries. - - * If false, don't hide any entries. - * If true, hide internal entries and entries that contain a local - variable ``__tracebackhide__ = True``. - * If a callable, delegates the filtering to the callable. - - :param bool funcargs: - Show function arguments per traceback entry. - - :param bool truncate_locals: - Whether to show a size-limited `repr()` of locals, or a full - pretty-printing. - - :param bool truncate_args: - Whether to show a size-limited truncated `repr()` of function - arguments, or a full pretty-printing. - - :param bool chain: - If chained exceptions should be shown. - - .. versionchanged:: 3.9 - - Added the ``chain`` parameter. - """ - if style == "native": - return ReprExceptionInfo( - reprtraceback=ReprTracebackNative( - format_exception( - self.type, - self.value, - self.traceback[0]._rawentry if self.traceback else None, - ) - ), - reprcrash=self._getreprcrash(), - ) - - fmt = ExceptionInfoFormatter( - showlocals=showlocals, - style=style, - abspath=abspath, - tbfilter=tbfilter, - funcargs=funcargs, - truncate_locals=truncate_locals, - truncate_args=truncate_args, - chain=chain, - ) - return fmt.repr_excinfo(self) - - def match(self, regexp: str | re.Pattern[str]) -> Literal[True]: - """Check whether the regular expression `regexp` matches the string - representation of the exception using :func:`python:re.search`. - - If it matches `True` is returned, otherwise an `AssertionError` is raised. - """ - __tracebackhide__ = True - value = stringify_exception(self.value) - msg = ( - f"Regex pattern did not match.\n" - f" Expected regex: {regexp!r}\n" - f" Actual message: {value!r}" - ) - if regexp == value: - msg += "\n Did you mean to `re.escape()` the regex?" - assert re.search(regexp, value), msg - # Return True to allow for "assert excinfo.match()". - return True - - def _group_contains( - self, - exc_group: BaseExceptionGroup[BaseException], - expected_exception: EXCEPTION_OR_MORE, - match: str | re.Pattern[str] | None, - target_depth: int | None = None, - current_depth: int = 1, - ) -> bool: - """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" - if (target_depth is not None) and (current_depth > target_depth): - # already descended past the target depth - return False - for exc in exc_group.exceptions: - if isinstance(exc, BaseExceptionGroup): - if self._group_contains( - exc, expected_exception, match, target_depth, current_depth + 1 - ): - return True - if (target_depth is not None) and (current_depth != target_depth): - # not at the target depth, no match - continue - if not isinstance(exc, expected_exception): - continue - if match is not None: - value = stringify_exception(exc) - if not re.search(match, value): - continue - return True - return False - - def group_contains( - self, - expected_exception: EXCEPTION_OR_MORE, - *, - match: str | re.Pattern[str] | None = None, - depth: int | None = None, - ) -> bool: - """Check whether a captured exception group contains a matching exception. - - :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception: - The expected exception type, or a tuple if one of multiple possible - exception types are expected. - - :param str | re.Pattern[str] | None match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception and its `PEP-678 ` `__notes__` - using :func:`re.search`. - - To match a literal string that may contain :ref:`special characters - `, the pattern can first be escaped with :func:`re.escape`. - - :param Optional[int] depth: - If `None`, will search for a matching exception at any nesting depth. - If >= 1, will only match an exception if it's at the specified depth (depth = 1 being - the exceptions contained within the topmost exception group). - - .. versionadded:: 8.0 - - .. warning:: - This helper makes it easy to check for the presence of specific exceptions, - but it is very bad for checking that the group does *not* contain - *any other exceptions*. - You should instead consider using :class:`pytest.RaisesGroup` - - """ - msg = "Captured exception is not an instance of `BaseExceptionGroup`" - assert isinstance(self.value, BaseExceptionGroup), msg - msg = "`depth` must be >= 1 if specified" - assert (depth is None) or (depth >= 1), msg - return self._group_contains(self.value, expected_exception, match, depth) - - -# Type alias for the `tbfilter` setting: -# bool: If True, it should be filtered using Traceback.filter() -# callable: A callable that takes an ExceptionInfo and returns the filtered traceback. -TracebackFilter: TypeAlias = bool | Callable[[ExceptionInfo[BaseException]], Traceback] - - -@dataclasses.dataclass -class ExceptionInfoFormatter: - """Helper object to format ExceptionInfo's and individual exception parts - into TerminalRepr's. - - See :func:`ExceptionInfo.getrepr` for parameters. - """ - - # for traceback entries - flow_marker: ClassVar = ">" - fail_marker: ClassVar = "E" - - showlocals: bool = False - # Note: "native" is handled outside of ExceptionInfoFormatter. - style: TracebackStyle = "long" - abspath: bool = True - tbfilter: TracebackFilter = True - funcargs: bool = False - truncate_locals: bool = True - truncate_args: bool = True - chain: bool = True - - astcache: dict[str | Path, ast.AST] = dataclasses.field( - default_factory=dict, init=False, repr=False - ) - - def _getindent(self, source: Source) -> int: - # Figure out indent for the given source. - try: - s = str(source.getstatement(len(source) - 1)) - except KeyboardInterrupt: - raise - except BaseException: - try: - s = str(source[-1]) - except KeyboardInterrupt: - raise - except BaseException: - return 0 - return 4 + (len(s) - len(s.lstrip())) - - def _getentrysource(self, entry: TracebackEntry) -> Source | None: - source = entry.getsource(self.astcache) - if source is not None: - source = source.deindent() - return source - - def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: - if self.funcargs: - args = [] - for argname, argvalue in entry.frame.getargs(var=True): - if self.truncate_args: - str_repr = saferepr(argvalue) - else: - str_repr = saferepr(argvalue, maxsize=None) - args.append((argname, str_repr)) - return ReprFuncArgs(args) - return None - - def get_source( - self, - source: Source | None, - line_index: int = -1, - excinfo: ExceptionInfo[BaseException] | None = None, - short: bool = False, - end_line_index: int | None = None, - colno: int | None = None, - end_colno: int | None = None, - ) -> list[str]: - """Return formatted and marked up source lines.""" - lines = [] - if source is not None and line_index < 0: - line_index += len(source) - if source is None or line_index >= len(source.lines) or line_index < 0: - # `line_index` could still be outside `range(len(source.lines))` if - # we're processing AST with pathological position attributes. - source = Source("???") - line_index = 0 - space_prefix = " " - if short: - lines.append(space_prefix + source.lines[line_index].strip()) - lines.extend( - self.get_highlight_arrows_for_line( - raw_line=source.raw_lines[line_index], - line=source.lines[line_index].strip(), - lineno=line_index, - end_lineno=end_line_index, - colno=colno, - end_colno=end_colno, - ) - ) - else: - for line in source.lines[:line_index]: - lines.append(space_prefix + line) - lines.append(self.flow_marker + " " + source.lines[line_index]) - lines.extend( - self.get_highlight_arrows_for_line( - raw_line=source.raw_lines[line_index], - line=source.lines[line_index], - lineno=line_index, - end_lineno=end_line_index, - colno=colno, - end_colno=end_colno, - ) - ) - for line in source.lines[line_index + 1 :]: - lines.append(space_prefix + line) - if excinfo is not None: - indent = 4 if short else self._getindent(source) - lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) - return lines - - def get_highlight_arrows_for_line( - self, - line: str, - raw_line: str, - lineno: int | None, - end_lineno: int | None, - colno: int | None, - end_colno: int | None, - ) -> list[str]: - """Return characters highlighting a source line. - - Example with colno and end_colno pointing to the bar expression: - "foo() + bar()" - returns " ^^^^^" - """ - if lineno != end_lineno: - # Don't handle expressions that span multiple lines. - return [] - if colno is None or end_colno is None: - # Can't do anything without column information. - return [] - - num_stripped_chars = len(raw_line) - len(line) - - start_char_offset = _byte_offset_to_character_offset(raw_line, colno) - end_char_offset = _byte_offset_to_character_offset(raw_line, end_colno) - num_carets = end_char_offset - start_char_offset - # If the highlight would span the whole line, it is redundant, don't - # show it. - if num_carets >= len(line.strip()): - return [] - - highlights = " " - highlights += " " * (start_char_offset - num_stripped_chars + 1) - highlights += "^" * num_carets - return [highlights] - - def get_exconly( - self, - excinfo: ExceptionInfo[BaseException], - indent: int = 4, - markall: bool = False, - ) -> list[str]: - lines = [] - indentstr = " " * indent - # Get the real exception information out. - exlines = excinfo.exconly(tryshort=True).split("\n") - failindent = self.fail_marker + indentstr[1:] - for line in exlines: - lines.append(failindent + line) - if not markall: - failindent = indentstr - return lines - - def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: - if self.showlocals: - lines = [] - # Variables starting with `@` are helpers injected by assertion - # rewriting, not user variables, so hide them. - keys = [loc for loc in locals if loc[0] != "@"] - keys.sort() - for name in keys: - value = locals[name] - if name == "__builtins__": - lines.append("__builtins__ = ") - else: - # This formatting could all be handled by the - # _repr() function, which is only reprlib.Repr in - # disguise, so is very configurable. - if self.truncate_locals: - str_repr = saferepr(value) - else: - str_repr = safeformat(value) - # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): - lines.append(f"{name:<10} = {str_repr}") - # else: - # self._line("%-10s =\\" % (name,)) - # # XXX - # pprint.pprint(value, stream=self.excinfowriter) - return ReprLocals(lines) - return None - - def repr_traceback_entry( - self, - entry: TracebackEntry | None, - excinfo: ExceptionInfo[BaseException] | None = None, - ) -> ReprEntry: - lines: list[str] = [] - style = ( - entry._repr_style - if entry is not None and entry._repr_style is not None - else self.style - ) - if style in ("short", "long") and entry is not None: - source = self._getentrysource(entry) - if source is None: - source = Source("???") - line_index = 0 - end_line_index, colno, end_colno = None, None, None - else: - line_index = entry.relline - end_line_index = entry.end_lineno_relative - colno = entry.colno - end_colno = entry.end_colno - short = style == "short" - reprargs = self.repr_args(entry) if not short else None - s = self.get_source( - source=source, - line_index=line_index, - excinfo=excinfo, - short=short, - end_line_index=end_line_index, - colno=colno, - end_colno=end_colno, - ) - lines.extend(s) - if short: - message = f"in {entry.name}" - else: - message = (excinfo and excinfo.typename) or "" - entry_path = entry.path - path = self._makepath(entry_path) - reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) - localsrepr = self.repr_locals(entry.locals) - return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) - elif style == "value": - if excinfo: - lines.extend(str(excinfo.value).split("\n")) - return ReprEntry(lines, None, None, None, style) - else: - if excinfo: - lines.extend(self.get_exconly(excinfo, indent=4)) - return ReprEntry(lines, None, None, None, style) - - def _makepath(self, path: Path | str) -> str: - if not self.abspath and isinstance(path, Path): - try: - np = bestrelpath(Path.cwd(), path) - except OSError: - return str(path) - if len(np) < len(str(path)): - return np - return str(path) - - def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: - traceback = filter_excinfo_traceback(self.tbfilter, excinfo) - - if isinstance(excinfo.value, RecursionError): - traceback, extraline = self._truncate_recursive_traceback(traceback) - else: - extraline = None - - if not traceback: - if extraline is None: - extraline = "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames." - entries = [self.repr_traceback_entry(None, excinfo)] - return ReprTraceback(entries, extraline, style=self.style) - - last = traceback[-1] - if self.style == "value": - entries = [self.repr_traceback_entry(last, excinfo)] - return ReprTraceback(entries, None, style=self.style) - - entries = [ - self.repr_traceback_entry(entry, excinfo if last == entry else None) - for entry in traceback - ] - return ReprTraceback(entries, extraline, style=self.style) - - def _truncate_recursive_traceback( - self, traceback: Traceback - ) -> tuple[Traceback, str | None]: - """Truncate the given recursive traceback trying to find the starting - point of the recursion. - - The detection is done by going through each traceback entry and - finding the point in which the locals of the frame are equal to the - locals of a previous frame (see ``recursionindex()``). - - Handle the situation where the recursion process might raise an - exception (for example comparing numpy arrays using equality raises a - TypeError), in which case we do our best to warn the user of the - error and show a limited traceback. - """ - try: - recursionindex = traceback.recursionindex() - except Exception as e: - max_frames = 10 - extraline: str | None = ( - "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" - " The following exception happened when comparing locals in the stack frame:\n" - f" {type(e).__name__}: {e!s}\n" - f" Displaying first and last {max_frames} stack frames out of {len(traceback)}." - ) - # Type ignored because adding two instances of a List subtype - # currently incorrectly has type List instead of the subtype. - traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore - else: - if recursionindex is not None: - extraline = "!!! Recursion detected (same locals & position)" - traceback = traceback[: recursionindex + 1] - else: - extraline = None - - return traceback, extraline - - def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: - repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] - e: BaseException | None = excinfo.value - excinfo_: ExceptionInfo[BaseException] | None = excinfo - description = None - seen: set[int] = set() - while e is not None and id(e) not in seen: - seen.add(id(e)) - - if excinfo_: - # Fall back to native traceback as a temporary workaround until - # full support for exception groups added to ExceptionInfo. - # See https://github.com/pytest-dev/pytest/issues/9159 - reprtraceback: ReprTraceback | ReprTracebackNative - if isinstance(e, BaseExceptionGroup): - # don't filter any sub-exceptions since they shouldn't have any internal frames - traceback = filter_excinfo_traceback(self.tbfilter, excinfo) - extraline = ( - "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames." - if not traceback - else None - ) - reprtraceback = ReprTracebackNative( - format_exception( - type(excinfo.value), - excinfo.value, - traceback[0]._rawentry if traceback else None, - ), - extraline=extraline, - ) - - else: - reprtraceback = self.repr_traceback(excinfo_) - reprcrash = excinfo_._getreprcrash() - else: - # Fallback to native repr if the exception doesn't have a traceback: - # ExceptionInfo objects require a full traceback to work. - reprtraceback = ReprTracebackNative(format_exception(type(e), e, None)) - reprcrash = None - repr_chain.append((reprtraceback, reprcrash, description)) - - if e.__cause__ is not None and self.chain: - e = e.__cause__ - excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None - description = "The above exception was the direct cause of the following exception:" - elif ( - e.__context__ is not None and not e.__suppress_context__ and self.chain - ): - e = e.__context__ - excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None - description = "During handling of the above exception, another exception occurred:" - else: - e = None - repr_chain.reverse() - return ExceptionChainRepr(repr_chain) - - -@dataclasses.dataclass(eq=False) -class TerminalRepr: - """Base class for terminal representations -- pieces of data that display - themselves to a terminal.""" - - def __str__(self) -> str: - # FYI this is called from pytest-xdist's serialization of exception - # information. - io = StringIO() - tw = TerminalWriter(file=io) - self.toterminal(tw) - return io.getvalue().strip() - - def __repr__(self) -> str: - return f"<{self.__class__} instance at {id(self):0x}>" - - def toterminal(self, tw: TerminalWriter) -> None: - raise NotImplementedError() - - -@dataclasses.dataclass(eq=False) -class ExceptionRepr(TerminalRepr): - """Base class for exception terminal representations. - - The representation generally contains: - - The exception traceback (`reprtraceback`) - - The exception message and location (`reprcrash`) - - Separated, titled sections with additional data (pytest core doesn't use - this currently). - """ - - reprtraceback: ReprTraceback - reprcrash: ReprFileLocation | None - sections: list[tuple[str, str, str]] = dataclasses.field( - init=False, default_factory=list - ) - - def addsection(self, name: str, content: str, sep: str = "-") -> None: - self.sections.append((name, content, sep)) - - def toterminal(self, tw: TerminalWriter) -> None: - for name, content, sep in self.sections: - tw.sep(sep, name) - tw.line(content) - - -@dataclasses.dataclass(eq=False) -class ExceptionChainRepr(ExceptionRepr): - """A chain of exceptions, separated by descriptions (e.g. "The above - exception was the direct cause of the following exception").""" - - chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] - - def __init__( - self, - chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], - ) -> None: - # reprcrash and reprtraceback of the outermost (the newest) exception - # in the chain. - super().__init__( - reprtraceback=chain[-1][0], - reprcrash=chain[-1][1], - ) - self.chain = chain - - def toterminal(self, tw: TerminalWriter) -> None: - for reprtraceback, reprcrash, description in self.chain: - reprtraceback.toterminal(tw) - if description is not None: - tw.line("") - tw.line(description, yellow=True) - super().toterminal(tw) - - -@dataclasses.dataclass(eq=False) -class ReprExceptionInfo(ExceptionRepr): - """A single exception with optional extra details (function arguments, - function locals, file location) and possible extra line and sections emitted - at the end.""" - - def toterminal(self, tw: TerminalWriter) -> None: - self.reprtraceback.toterminal(tw) - super().toterminal(tw) - - -@dataclasses.dataclass(eq=False) -class ReprTraceback(TerminalRepr): - """A traceback with optional extra details (function arguments, function - locals, file location) and possible extra line emitted at the end.""" - - reprentries: Sequence[ReprEntry | ReprEntryNative] - extraline: str | None - style: TracebackStyle - - entrysep: ClassVar = "_ " - - def toterminal(self, tw: TerminalWriter) -> None: - # The entries might have different styles. - for i, entry in enumerate(self.reprentries): - if entry.style == "long": - tw.line("") - entry.toterminal(tw) - if i < len(self.reprentries) - 1: - next_entry = self.reprentries[i + 1] - if entry.style == "long" or ( - entry.style == "short" and next_entry.style == "long" - ): - tw.sep(self.entrysep) - - if self.extraline: - tw.line(self.extraline) - - -class ReprTracebackNative(ReprTraceback): - """A traceback in native style. - - The lines are emitted as is; uses a single entry for the entire native - traceback. - """ - - def __init__(self, tblines: Sequence[str], *, extraline: str | None = None) -> None: - self.reprentries = [ReprEntryNative(tblines)] - self.extraline = extraline - self.style = "native" - - -@dataclasses.dataclass(eq=False) -class ReprEntryNative(TerminalRepr): - """An entry in a traceback in native style. - - Emits the lines as is. The lines are assumed to include the line separators. - - [Note that we currently use a single "entry" for the entire native - traceback, so this is a bit misleading, but there's no point trying to parse - or split a native traceback.] - """ - - lines: Sequence[str] - - style: ClassVar[TracebackStyle] = "native" - - def toterminal(self, tw: TerminalWriter) -> None: - tw.write("".join(self.lines)) - - -@dataclasses.dataclass(eq=False) -class ReprEntry(TerminalRepr): - """An entry in a traceback with possible extra details (function arguments, - function locals, source snippet, function's file and line).""" - - lines: Sequence[str] - reprfuncargs: ReprFuncArgs | None - reprlocals: ReprLocals | None - reprfileloc: ReprFileLocation | None - style: TracebackStyle - - def _write_entry_lines(self, tw: TerminalWriter) -> None: - """Write the source code portions of a list of traceback entries with syntax highlighting. - - Usually entries are lines like these: - - " x = 1" - "> assert x == 2" - "E assert 1 == 2" - - This function takes care of rendering the "source" portions of it (the lines without - the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" - character, as doing so might break line continuations. - """ - if not self.lines: - return - - if self.style == "value": - # Using tw.write instead of tw.line for testing purposes due to TWMock implementation; - # lines written with TWMock.line and TWMock._write_source cannot be distinguished - # from each other, whereas lines written with TWMock.write are marked with TWMock.WRITE - for line in self.lines: - tw.write(line) - tw.write("\n") - return - - # separate indents and source lines that are not failures: we want to - # highlight the code but not the indentation, which may contain markers - # such as "> assert 0" - fail_marker = f"{ExceptionInfoFormatter.fail_marker} " - indent_size = len(fail_marker) - indents: list[str] = [] - source_lines: list[str] = [] - failure_lines: list[str] = [] - for index, line in enumerate(self.lines): - is_failure_line = line.startswith(fail_marker) - if is_failure_line: - # from this point on all lines are considered part of the failure - failure_lines.extend(self.lines[index:]) - break - else: - indents.append(line[:indent_size]) - source_lines.append(line[indent_size:]) - - tw._write_source(source_lines, indents) - - # failure lines are always completely red and bold - for line in failure_lines: - tw.line(line, bold=True, red=True) - - def toterminal(self, tw: TerminalWriter) -> None: - if self.style == "short": - if self.reprfileloc: - self.reprfileloc.toterminal(tw) - self._write_entry_lines(tw) - if self.reprlocals: - self.reprlocals.toterminal(tw, indent=" " * 8) - return - - if self.reprfuncargs: - self.reprfuncargs.toterminal(tw) - - self._write_entry_lines(tw) - - if self.reprlocals: - tw.line("") - self.reprlocals.toterminal(tw) - if self.reprfileloc: - if self.lines: - tw.line("") - self.reprfileloc.toterminal(tw) - - def __str__(self) -> str: - return "{}\n{}\n{}".format( - "\n".join(self.lines), self.reprlocals, self.reprfileloc - ) - - -@dataclasses.dataclass(eq=False) -class ReprFileLocation(TerminalRepr): - """A message at a file location, using the `:: ` - format that most editors understand. - - Only the first line of the message is emitted. - """ - - path: str - lineno: int - message: str - - def __post_init__(self) -> None: - self.path = str(self.path) - - def toterminal(self, tw: TerminalWriter) -> None: - msg = self.message - i = msg.find("\n") - if i != -1: - msg = msg[:i] - tw.write(self.path, bold=True, red=True) - tw.line(f":{self.lineno}: {msg}") - - -@dataclasses.dataclass(eq=False) -class ReprLocals(TerminalRepr): - """Function local variables (pre-formatted).""" - - lines: Sequence[str] - - def toterminal(self, tw: TerminalWriter, indent: str = "") -> None: - for line in self.lines: - tw.line(indent + line) - - -@dataclasses.dataclass(eq=False) -class ReprFuncArgs(TerminalRepr): - """Function arguments - name = value, comma separated.""" - - args: Sequence[tuple[str, object]] - - def toterminal(self, tw: TerminalWriter) -> None: - if self.args: - linesofar = "" - for name, value in self.args: - ns = f"{name} = {value}" - if len(ns) + len(linesofar) + 2 > tw.fullwidth: - if linesofar: - tw.line(linesofar) - linesofar = ns - else: - if linesofar: - linesofar += ", " + ns - else: - linesofar = ns - if linesofar: - tw.line(linesofar) - tw.line("") - - -def getfslineno(obj: object) -> tuple[str | Path, int]: - """Return source location (path, lineno) for the given object. - - If the source cannot be determined return ("", -1). - - The line number is 0-based. - """ - # xxx let decorators etc specify a sane ordering - # NOTE: this used to be done in _pytest.compat.getfslineno, initially added - # in 6ec13a2b9. It ("place_as") appears to be something very custom. - obj = get_real_func(obj) - if hasattr(obj, "place_as"): - obj = obj.place_as - - try: - code = Code.from_function(obj) - except TypeError: - try: - fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] - except TypeError: - return "", -1 - - fspath = (fn and absolutepath(fn)) or "" - lineno = -1 - if fspath: - try: - _, lineno = findsource(obj) - except OSError: - pass - return fspath, lineno - - return code.path, code.firstlineno - - -def _byte_offset_to_character_offset(str, offset): - """Converts a byte based offset in a string to a code-point.""" - as_utf8 = str.encode("utf-8") - return len(as_utf8[:offset].decode("utf-8", errors="replace")) - - -# Relative paths that we use to filter traceback entries from appearing to the user; -# see filter_traceback. -# note: if we need to add more paths than what we have now we should probably use a list -# for better maintenance. - -_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) -# pluggy is either a package or a single module depending on the version -if _PLUGGY_DIR.name == "__init__.py": - _PLUGGY_DIR = _PLUGGY_DIR.parent -_PYTEST_DIR = Path(_pytest.__file__).parent - - -def filter_traceback(entry: TracebackEntry) -> bool: - """Return True if a TracebackEntry instance should be included in tracebacks. - - We hide traceback entries of: - - * dynamically generated code (no code to show up for it); - * internal traceback from pytest or its internal libraries, py and pluggy. - """ - # entry.path might sometimes return a str object when the entry - # points to dynamically generated code. - # See https://bitbucket.org/pytest-dev/py/issues/71. - raw_filename = entry.frame.code.raw.co_filename - is_generated = "<" in raw_filename and ">" in raw_filename - if is_generated: - return False - - # entry.path might point to a non-existing file, in which case it will - # also return a str object. See #1133. - p = Path(entry.path) - - parents = p.parents - if _PLUGGY_DIR in parents: - return False - if _PYTEST_DIR in parents: - return False - - return True - - -def filter_excinfo_traceback( - tbfilter: TracebackFilter, excinfo: ExceptionInfo[BaseException] -) -> Traceback: - """Filter the exception traceback in ``excinfo`` according to ``tbfilter``.""" - if callable(tbfilter): - return tbfilter(excinfo) - elif tbfilter: - return excinfo.traceback.filter(excinfo) - else: - return excinfo.traceback diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/source.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_code/source.py deleted file mode 100644 index cbadf66..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_code/source.py +++ /dev/null @@ -1,228 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import ast -from bisect import bisect_right -from collections.abc import Iterable -from collections.abc import Iterator -import inspect -import textwrap -import tokenize -import types -from typing import overload -import warnings - - -class Source: - """An immutable object holding a source code fragment. - - When using Source(...), the source lines are deindented. - """ - - def __init__(self, obj: object = None) -> None: - if not obj: - self.lines: list[str] = [] - self.raw_lines: list[str] = [] - elif isinstance(obj, Source): - self.lines = obj.lines - self.raw_lines = obj.raw_lines - elif isinstance(obj, tuple | list): - self.lines = deindent(x.rstrip("\n") for x in obj) - self.raw_lines = list(x.rstrip("\n") for x in obj) - elif isinstance(obj, str): - self.lines = deindent(obj.split("\n")) - self.raw_lines = obj.split("\n") - else: - try: - rawcode = getrawcode(obj) - src = inspect.getsource(rawcode) - except TypeError: - src = inspect.getsource(obj) # type: ignore[arg-type] - self.lines = deindent(src.split("\n")) - self.raw_lines = src.split("\n") - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Source): - return NotImplemented - return self.lines == other.lines - - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore - - @overload - def __getitem__(self, key: int) -> str: ... - - @overload - def __getitem__(self, key: slice) -> Source: ... - - def __getitem__(self, key: int | slice) -> str | Source: - if isinstance(key, int): - return self.lines[key] - else: - if key.step not in (None, 1): - raise IndexError("cannot slice a Source with a step") - newsource = Source() - newsource.lines = self.lines[key.start : key.stop] - newsource.raw_lines = self.raw_lines[key.start : key.stop] - return newsource - - def __iter__(self) -> Iterator[str]: - return iter(self.lines) - - def __len__(self) -> int: - return len(self.lines) - - def strip(self) -> Source: - """Return new Source object with trailing and leading blank lines removed.""" - start, end = 0, len(self) - while start < end and not self.lines[start].strip(): - start += 1 - while end > start and not self.lines[end - 1].strip(): - end -= 1 - source = Source() - source.raw_lines = self.raw_lines - source.lines[:] = self.lines[start:end] - return source - - def indent(self, indent: str = " " * 4) -> Source: - """Return a copy of the source object with all lines indented by the - given indent-string.""" - newsource = Source() - newsource.raw_lines = self.raw_lines - newsource.lines = [(indent + line) for line in self.lines] - return newsource - - def getstatement(self, lineno: int) -> Source: - """Return Source statement which contains the given linenumber - (counted from 0).""" - start, end = self.getstatementrange(lineno) - return self[start:end] - - def getstatementrange(self, lineno: int) -> tuple[int, int]: - """Return (start, end) tuple which spans the minimal statement region - which containing the given lineno.""" - if not (0 <= lineno < len(self)): - raise IndexError("lineno out of range") - _ast, start, end = getstatementrange_ast(lineno, self) - return start, end - - def deindent(self) -> Source: - """Return a new Source object deindented.""" - newsource = Source() - newsource.lines[:] = deindent(self.lines) - newsource.raw_lines = self.raw_lines - return newsource - - def __str__(self) -> str: - return "\n".join(self.lines) - - -# -# helper functions -# - - -def findsource(obj) -> tuple[Source | None, int]: - try: - sourcelines, lineno = inspect.findsource(obj) - except Exception: - return None, -1 - source = Source() - source.lines = [line.rstrip() for line in sourcelines] - source.raw_lines = sourcelines - return source, lineno - - -def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: - """Return code object for given function.""" - try: - return obj.__code__ # type: ignore[attr-defined,no-any-return] - except AttributeError: - pass - if trycall: - call = getattr(obj, "__call__", None) - if call and not isinstance(obj, type): - return getrawcode(call, trycall=False) - raise TypeError(f"could not get code object for {obj!r}") - - -def deindent(lines: Iterable[str]) -> list[str]: - return textwrap.dedent("\n".join(lines)).splitlines() - - -def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: - # Flatten all statements and except handlers into one lineno-list. - # AST's line numbers start indexing at 1. - values: list[int] = [] - for x in ast.walk(node): - if isinstance(x, ast.stmt | ast.ExceptHandler): - # The lineno points to the class/def, so need to include the decorators. - if isinstance(x, ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef): - for d in x.decorator_list: - values.append(d.lineno - 1) - values.append(x.lineno - 1) - for name in ("finalbody", "orelse"): - val: list[ast.stmt] | None = getattr(x, name, None) - if val: - # Treat the finally/orelse part as its own statement. - values.append(val[0].lineno - 1 - 1) - values.sort() - insert_index = bisect_right(values, lineno) - if insert_index == 0: - return 0, None - start = values[insert_index - 1] - if insert_index >= len(values): - end = None - else: - end = values[insert_index] - return start, end - - -def getstatementrange_ast( - lineno: int, - source: Source, - assertion: bool = False, - astnode: ast.AST | None = None, -) -> tuple[ast.AST, int, int]: - if astnode is None: - content = str(source) - # See #4260: - # Don't produce duplicate warnings when compiling source to find AST. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - astnode = ast.parse(content, "source", "exec") - - start, end = get_statement_startend2(lineno, astnode) - # We need to correct the end: - # - ast-parsing strips comments - # - there might be empty lines - # - we might have lesser indented code blocks at the end - if end is None: - end = len(source.lines) - - if end > start + 1: - # Make sure we don't span differently indented code blocks - # by using the BlockFinder helper used which inspect.getsource() uses itself. - block_finder = inspect.BlockFinder() - # If we start with an indented line, put blockfinder to "started" mode. - block_finder.started = ( - bool(source.lines[start]) and source.lines[start][0].isspace() - ) - it = ((x + "\n") for x in source.lines[start:end]) - try: - for tok in tokenize.generate_tokens(lambda: next(it)): - block_finder.tokeneater(*tok) - except (inspect.EndOfBlock, IndentationError): - end = block_finder.last + start - except Exception: - pass - - # The end might still point to a comment or empty line, correct it. - end = min(end, len(source.lines)) - while end: - line = source.lines[end - 1].lstrip() - if line.startswith("#") or not line: - end -= 1 - else: - break - return astnode, start, end diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__init__.py deleted file mode 100644 index b0155b1..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -from .terminalwriter import get_terminal_width -from .terminalwriter import TerminalWriter - - -__all__ = [ - "TerminalWriter", - "get_terminal_width", -] diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 8fdc078..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/pprint.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/pprint.cpython-311.pyc deleted file mode 100644 index 8cf5f16..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/pprint.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/saferepr.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/saferepr.cpython-311.pyc deleted file mode 100644 index e8756bf..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/saferepr.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-311.pyc deleted file mode 100644 index 13d0a82..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-311.pyc deleted file mode 100644 index a1fb6b3..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/pprint.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/pprint.py deleted file mode 100644 index ec41b44..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/pprint.py +++ /dev/null @@ -1,673 +0,0 @@ -# mypy: allow-untyped-defs -# This module was imported from the cpython standard library -# (https://github.com/python/cpython/) at commit -# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12). -# -# -# Original Author: Fred L. Drake, Jr. -# fdrake@acm.org -# -# This is a simple little module I wrote to make life easier. I didn't -# see anything quite like it in the library, though I may have overlooked -# something. I wrote this when I was trying to read some heavily nested -# tuples with fairly non-descriptive content. This is modeled very much -# after Lisp/Scheme - style pretty-printing of lists. If you find it -# useful, thank small children who sleep at night. -from __future__ import annotations - -import collections as _collections -from collections.abc import Callable -from collections.abc import Iterator -import dataclasses as _dataclasses -from io import StringIO as _StringIO -import re -import types as _types -from typing import Any -from typing import IO - - -class _safe_key: - """Helper function for key functions when sorting unorderable objects. - - The wrapped-object will fallback to a Py2.x style comparison for - unorderable types (sorting first comparing the type name and then by - the obj ids). Does not work recursively, so dict.items() must have - _safe_key applied to both the key and the value. - - """ - - __slots__ = ["obj"] - - def __init__(self, obj): - self.obj = obj - - def __lt__(self, other): - try: - return self.obj < other.obj - except TypeError: - return (str(type(self.obj)), id(self.obj)) < ( - str(type(other.obj)), - id(other.obj), - ) - - -def _safe_tuple(t): - """Helper function for comparing 2-tuples""" - return _safe_key(t[0]), _safe_key(t[1]) - - -class PrettyPrinter: - def __init__( - self, - indent: int = 4, - width: int = 80, - depth: int | None = None, - ) -> None: - """Handle pretty printing operations onto a stream using a set of - configured parameters. - - indent - Number of spaces to indent for each level of nesting. - - width - Attempted maximum number of columns in the output. - - depth - The maximum depth to print out nested structures. - - """ - if indent < 0: - raise ValueError("indent must be >= 0") - if depth is not None and depth <= 0: - raise ValueError("depth must be > 0") - if not width: - raise ValueError("width must be != 0") - self._depth = depth - self._indent_per_level = indent - self._width = width - - def pformat(self, object: Any) -> str: - sio = _StringIO() - self._format(object, sio, 0, 0, set(), 0) - return sio.getvalue() - - def _format( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - objid = id(object) - if objid in context: - stream.write(_recursion(object)) - return - - p = self._dispatch.get(type(object).__repr__, None) - if p is not None: - context.add(objid) - p(self, object, stream, indent, allowance, context, level + 1) - context.remove(objid) - elif ( - _dataclasses.is_dataclass(object) - and not isinstance(object, type) - and object.__dataclass_params__.repr # type:ignore[attr-defined] - and - # Check dataclass has generated repr method. - hasattr(object.__repr__, "__wrapped__") - and "__create_fn__" in object.__repr__.__wrapped__.__qualname__ - ): - context.add(objid) - self._pprint_dataclass( - object, stream, indent, allowance, context, level + 1 - ) - context.remove(objid) - else: - stream.write(self._repr(object, context, level)) - - def _pprint_dataclass( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - cls_name = object.__class__.__name__ - items = [ - (f.name, getattr(object, f.name)) - for f in _dataclasses.fields(object) - if f.repr - ] - stream.write(cls_name + "(") - self._format_namespace_items(items, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch: dict[ - Callable[..., str], - Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None], - ] = {} - - def _pprint_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - write("{") - items = object.items() - self._format_dict_items(items, stream, indent, allowance, context, level) - write("}") - - _dispatch[dict.__repr__] = _pprint_dict - - def _pprint_ordered_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not len(object): - stream.write(repr(object)) - return - cls = object.__class__ - stream.write(cls.__name__ + "(") - self._pprint_dict(object, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict - - def _pprint_list( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write("[") - self._format_items(object, stream, indent, allowance, context, level) - stream.write("]") - - _dispatch[list.__repr__] = _pprint_list - - def _pprint_tuple( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write("(") - self._format_items(object, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[tuple.__repr__] = _pprint_tuple - - def _pprint_set( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not len(object): - stream.write(repr(object)) - return - typ = object.__class__ - if typ is set: - stream.write("{") - endchar = "}" - else: - stream.write(typ.__name__ + "({") - endchar = "})" - object = sorted(object, key=_safe_key) - self._format_items(object, stream, indent, allowance, context, level) - stream.write(endchar) - - _dispatch[set.__repr__] = _pprint_set - _dispatch[frozenset.__repr__] = _pprint_set - - def _pprint_str( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - if not len(object): - write(repr(object)) - return - chunks = [] - lines = object.splitlines(True) - if level == 1: - indent += 1 - allowance += 1 - max_width1 = max_width = self._width - indent - for i, line in enumerate(lines): - rep = repr(line) - if i == len(lines) - 1: - max_width1 -= allowance - if len(rep) <= max_width1: - chunks.append(rep) - else: - # A list of alternating (non-space, space) strings - parts = re.findall(r"\S*\s*", line) - assert parts - assert not parts[-1] - parts.pop() # drop empty last part - max_width2 = max_width - current = "" - for j, part in enumerate(parts): - candidate = current + part - if j == len(parts) - 1 and i == len(lines) - 1: - max_width2 -= allowance - if len(repr(candidate)) > max_width2: - if current: - chunks.append(repr(current)) - current = part - else: - current = candidate - if current: - chunks.append(repr(current)) - if len(chunks) == 1: - write(rep) - return - if level == 1: - write("(") - for i, rep in enumerate(chunks): - if i > 0: - write("\n" + " " * indent) - write(rep) - if level == 1: - write(")") - - _dispatch[str.__repr__] = _pprint_str - - def _pprint_bytes( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - if len(object) <= 4: - write(repr(object)) - return - parens = level == 1 - if parens: - indent += 1 - allowance += 1 - write("(") - delim = "" - for rep in _wrap_bytes_repr(object, self._width - indent, allowance): - write(delim) - write(rep) - if not delim: - delim = "\n" + " " * indent - if parens: - write(")") - - _dispatch[bytes.__repr__] = _pprint_bytes - - def _pprint_bytearray( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - write("bytearray(") - self._pprint_bytes( - bytes(object), stream, indent + 10, allowance + 1, context, level + 1 - ) - write(")") - - _dispatch[bytearray.__repr__] = _pprint_bytearray - - def _pprint_mappingproxy( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write("mappingproxy(") - self._format(object.copy(), stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy - - def _pprint_simplenamespace( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if type(object) is _types.SimpleNamespace: - # The SimpleNamespace repr is "namespace" instead of the class - # name, so we do the same here. For subclasses; use the class name. - cls_name = "namespace" - else: - cls_name = object.__class__.__name__ - items = object.__dict__.items() - stream.write(cls_name + "(") - self._format_namespace_items(items, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace - - def _format_dict_items( - self, - items: list[tuple[Any, Any]], - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not items: - return - - write = stream.write - item_indent = indent + self._indent_per_level - delimnl = "\n" + " " * item_indent - for key, ent in items: - write(delimnl) - write(self._repr(key, context, level)) - write(": ") - self._format(ent, stream, item_indent, 1, context, level) - write(",") - - write("\n" + " " * indent) - - def _format_namespace_items( - self, - items: list[tuple[Any, Any]], - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not items: - return - - write = stream.write - item_indent = indent + self._indent_per_level - delimnl = "\n" + " " * item_indent - for key, ent in items: - write(delimnl) - write(key) - write("=") - if id(ent) in context: - # Special-case representation of recursion to match standard - # recursive dataclass repr. - write("...") - else: - self._format( - ent, - stream, - item_indent + len(key) + 1, - 1, - context, - level, - ) - - write(",") - - write("\n" + " " * indent) - - def _format_items( - self, - items: list[Any], - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not items: - return - - write = stream.write - item_indent = indent + self._indent_per_level - delimnl = "\n" + " " * item_indent - - for item in items: - write(delimnl) - self._format(item, stream, item_indent, 1, context, level) - write(",") - - write("\n" + " " * indent) - - def _repr(self, object: Any, context: set[int], level: int) -> str: - return self._safe_repr(object, context.copy(), self._depth, level) - - def _pprint_default_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - rdf = self._repr(object.default_factory, context, level) - stream.write(f"{object.__class__.__name__}({rdf}, ") - self._pprint_dict(object, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict - - def _pprint_counter( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write(object.__class__.__name__ + "(") - - if object: - stream.write("{") - items = object.most_common() - self._format_dict_items(items, stream, indent, allowance, context, level) - stream.write("}") - - stream.write(")") - - _dispatch[_collections.Counter.__repr__] = _pprint_counter - - def _pprint_chain_map( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): - stream.write(repr(object)) - return - - stream.write(object.__class__.__name__ + "(") - self._format_items(object.maps, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map - - def _pprint_deque( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write(object.__class__.__name__ + "(") - if object.maxlen is not None: - stream.write(f"maxlen={object.maxlen}, ") - stream.write("[") - - self._format_items(object, stream, indent, allowance + 1, context, level) - stream.write("])") - - _dispatch[_collections.deque.__repr__] = _pprint_deque - - def _pprint_user_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - self._format(object.data, stream, indent, allowance, context, level - 1) - - _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict - - def _pprint_user_list( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - self._format(object.data, stream, indent, allowance, context, level - 1) - - _dispatch[_collections.UserList.__repr__] = _pprint_user_list - - def _pprint_user_string( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - self._format(object.data, stream, indent, allowance, context, level - 1) - - _dispatch[_collections.UserString.__repr__] = _pprint_user_string - - def _safe_repr( - self, object: Any, context: set[int], maxlevels: int | None, level: int - ) -> str: - typ = type(object) - if typ in _builtin_scalars: - return repr(object) - - r = getattr(typ, "__repr__", None) - - if issubclass(typ, dict) and r is dict.__repr__: - if not object: - return "{}" - objid = id(object) - if maxlevels and level >= maxlevels: - return "{...}" - if objid in context: - return _recursion(object) - context.add(objid) - components: list[str] = [] - append = components.append - level += 1 - for k, v in object.items(): - krepr = self._safe_repr(k, context, maxlevels, level) - vrepr = self._safe_repr(v, context, maxlevels, level) - append(f"{krepr}: {vrepr}") - context.remove(objid) - return "{{{}}}".format(", ".join(components)) - - if (issubclass(typ, list) and r is list.__repr__) or ( - issubclass(typ, tuple) and r is tuple.__repr__ - ): - if issubclass(typ, list): - if not object: - return "[]" - format = "[%s]" - elif len(object) == 1: - format = "(%s,)" - else: - if not object: - return "()" - format = "(%s)" - objid = id(object) - if maxlevels and level >= maxlevels: - return format % "..." - if objid in context: - return _recursion(object) - context.add(objid) - components = [] - append = components.append - level += 1 - for o in object: - orepr = self._safe_repr(o, context, maxlevels, level) - append(orepr) - context.remove(objid) - return format % ", ".join(components) - - return repr(object) - - -_builtin_scalars = frozenset( - {str, bytes, bytearray, float, complex, bool, type(None), int} -) - - -def _recursion(object: Any) -> str: - return f"" - - -def _wrap_bytes_repr(object: Any, width: int, allowance: int) -> Iterator[str]: - current = b"" - last = len(object) // 4 * 4 - for i in range(0, len(object), 4): - part = object[i : i + 4] - candidate = current + part - if i == last: - width -= allowance - if len(repr(candidate)) > width: - if current: - yield repr(current) - current = part - else: - current = candidate - if current: - yield repr(current) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/saferepr.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/saferepr.py deleted file mode 100644 index 3f5c956..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/saferepr.py +++ /dev/null @@ -1,155 +0,0 @@ -from __future__ import annotations - -from itertools import islice -import pprint -import reprlib - - -def _try_repr_or_str(obj: object) -> str: - try: - return repr(obj) - except (KeyboardInterrupt, SystemExit): - raise - except BaseException: - return f'{type(obj).__name__}("{obj}")' - - -def _format_repr_exception(exc: BaseException, obj: object) -> str: - try: - exc_info = _try_repr_or_str(exc) - except (KeyboardInterrupt, SystemExit): - raise - except BaseException as inner_exc: - exc_info = f"unpresentable exception ({_try_repr_or_str(inner_exc)})" - return ( - f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>" - ) - - -def _ellipsize(s: str, maxsize: int) -> str: - if len(s) > maxsize: - i = max(0, (maxsize - 3) // 2) - j = max(0, maxsize - 3 - i) - return s[:i] + "..." + s[len(s) - j :] - return s - - -class SafeRepr(reprlib.Repr): - """ - repr.Repr that limits the resulting size of repr() and includes - information on exceptions raised during the call. - """ - - def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: - """ - :param maxsize: - If not None, will truncate the resulting repr to that specific size, using ellipsis - somewhere in the middle to hide the extra text. - If None, will not impose any size limits on the returning repr. - """ - super().__init__() - # ``maxstring`` is used by the superclass, and needs to be an int; using a - # very large number in case maxsize is None, meaning we want to disable - # truncation. - self.maxstring = maxsize if maxsize is not None else 1_000_000_000 - self.maxsize = maxsize - self.use_ascii = use_ascii - - def repr(self, x: object) -> str: - try: - if self.use_ascii: - s = ascii(x) - else: - s = super().repr(x) - except (KeyboardInterrupt, SystemExit): - raise - except BaseException as exc: - s = _format_repr_exception(exc, x) - if self.maxsize is not None: - s = _ellipsize(s, self.maxsize) - return s - - def repr_instance(self, x: object, level: int) -> str: - try: - s = repr(x) - except (KeyboardInterrupt, SystemExit): - raise - except BaseException as exc: - s = _format_repr_exception(exc, x) - if self.maxsize is not None: - s = _ellipsize(s, self.maxsize) - - return s - - def repr_dict(self, x: dict[object, object], level: int) -> str: - """Represent a dict while preserving its insertion order. - - Differs from ``reprlib.Repr.repr_dict`` by iterating directly over ``x`` - rather than using the stdlib's sorting helper. - """ - fillvalue = "..." - n = len(x) - if n == 0: - return "{}" - if level <= 0: - return "{" + fillvalue + "}" - newlevel = level - 1 - repr1 = self.repr1 - pieces = [] - for key in islice(x, self.maxdict): - keyrepr = repr1(key, newlevel) - valrepr = repr1(x[key], newlevel) - pieces.append(f"{keyrepr}: {valrepr}") - if n > self.maxdict: - pieces.append(fillvalue) - return "{" + ", ".join(pieces) + "}" - - -def safeformat(obj: object) -> str: - """Return a pretty printed string for the given object. - - Failing __repr__ functions of user instances will be represented - with a short exception info. - """ - try: - return pprint.pformat(obj) - except Exception as exc: - return _format_repr_exception(exc, obj) - - -# Maximum size of overall repr of objects to display during assertion errors. -DEFAULT_REPR_MAX_SIZE = 240 - - -def saferepr( - obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False -) -> str: - """Return a size-limited safe repr-string for the given object. - - Failing __repr__ functions of user instances will be represented - with a short exception info and 'saferepr' generally takes - care to never raise exceptions itself. - - This function is a wrapper around the Repr/reprlib functionality of the - stdlib. - """ - return SafeRepr(maxsize, use_ascii).repr(obj) - - -def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: - """Return an unlimited-size safe repr-string for the given object. - - As with saferepr, failing __repr__ functions of user instances - will be represented with a short exception info. - - This function is a wrapper around simple repr. - - Note: a cleaner solution would be to alter ``saferepr``this way - when maxsize=None, but that might affect some other code. - """ - try: - if use_ascii: - return ascii(obj) - return repr(obj) - except Exception as exc: - return _format_repr_exception(exc, obj) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/terminalwriter.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/terminalwriter.py deleted file mode 100644 index 9191b4e..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/terminalwriter.py +++ /dev/null @@ -1,258 +0,0 @@ -"""Helper functions for writing to terminals and files.""" - -from __future__ import annotations - -from collections.abc import Sequence -import os -import shutil -import sys -from typing import final -from typing import Literal -from typing import TextIO - -import pygments -from pygments.formatters.terminal import TerminalFormatter -from pygments.lexer import Lexer -from pygments.lexers.diff import DiffLexer -from pygments.lexers.python import PythonLexer - -from ..compat import assert_never -from .wcwidth import wcswidth - - -# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. - - -def get_terminal_width() -> int: - width, _ = shutil.get_terminal_size(fallback=(80, 24)) - - # The Windows get_terminal_size may be bogus, let's sanify a bit. - if width < 40: - width = 80 - - return width - - -def should_do_markup(file: TextIO) -> bool: - if os.environ.get("PY_COLORS") == "1": - return True - if os.environ.get("PY_COLORS") == "0": - return False - if os.environ.get("NO_COLOR"): - return False - if os.environ.get("FORCE_COLOR"): - return True - return ( - hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" - ) - - -@final -class TerminalWriter: - _esctable = dict( - black=30, - red=31, - green=32, - yellow=33, - blue=34, - purple=35, - cyan=36, - white=37, - Black=40, - Red=41, - Green=42, - Yellow=43, - Blue=44, - Purple=45, - Cyan=46, - White=47, - bold=1, - light=2, - blink=5, - invert=7, - ) - - def __init__(self, file: TextIO | None = None) -> None: - if file is None: - file = sys.stdout - if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": - try: - import colorama - except ImportError: - pass - else: - file = colorama.AnsiToWin32(file).stream - assert file is not None - self._file = file - self.hasmarkup = should_do_markup(file) - self._current_line = "" - self._terminal_width: int | None = None - self.code_highlight = True - - @property - def fullwidth(self) -> int: - if self._terminal_width is not None: - return self._terminal_width - return get_terminal_width() - - @fullwidth.setter - def fullwidth(self, value: int) -> None: - self._terminal_width = value - - @property - def width_of_current_line(self) -> int: - """Return an estimate of the width so far in the current line.""" - return wcswidth(self._current_line) - - def markup(self, text: str, **markup: bool) -> str: - for name in markup: - if name not in self._esctable: - raise ValueError(f"unknown markup: {name!r}") - if self.hasmarkup: - esc = [self._esctable[name] for name, on in markup.items() if on] - if esc: - text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" - return text - - def sep( - self, - sepchar: str, - title: str | None = None, - fullwidth: int | None = None, - **markup: bool, - ) -> None: - if fullwidth is None: - fullwidth = self.fullwidth - # The goal is to have the line be as long as possible - # under the condition that len(line) <= fullwidth. - if sys.platform == "win32": - # If we print in the last column on windows we are on a - # new line but there is no way to verify/neutralize this - # (we may not know the exact line width). - # So let's be defensive to avoid empty lines in the output. - fullwidth -= 1 - if title is not None: - # we want 2 + 2*len(fill) + len(title) <= fullwidth - # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth - # 2*len(sepchar)*N <= fullwidth - len(title) - 2 - # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) - N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1) - fill = sepchar * N - line = f"{fill} {title} {fill}" - else: - # we want len(sepchar)*N <= fullwidth - # i.e. N <= fullwidth // len(sepchar) - line = sepchar * (fullwidth // len(sepchar)) - # In some situations there is room for an extra sepchar at the right, - # in particular if we consider that with a sepchar like "_ " the - # trailing space is not important at the end of the line. - if len(line) + len(sepchar.rstrip()) <= fullwidth: - line += sepchar.rstrip() - - self.line(line, **markup) - - def write(self, msg: str, *, flush: bool = False, **markup: bool) -> None: - if msg: - current_line = msg.rsplit("\n", 1)[-1] - if "\n" in msg: - self._current_line = current_line - else: - self._current_line += current_line - - msg = self.markup(msg, **markup) - - self.write_raw(msg, flush=flush) - - def write_raw(self, msg: str, *, flush: bool = False) -> None: - try: - self._file.write(msg) - except UnicodeEncodeError: - # Some environments don't support printing general Unicode - # strings, due to misconfiguration or otherwise; in that case, - # print the string escaped to ASCII. - # When the Unicode situation improves we should consider - # letting the error propagate instead of masking it (see #7475 - # for one brief attempt). - msg = msg.encode("unicode-escape").decode("ascii") - self._file.write(msg) - - if flush: - self.flush() - - def line(self, s: str = "", **markup: bool) -> None: - self.write(s, **markup) - self.write("\n") - - def flush(self) -> None: - self._file.flush() - - def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> None: - """Write lines of source code possibly highlighted. - - Keeping this private for now because the API is clunky. We should discuss how - to evolve the terminal writer so we can have more precise color support, for example - being able to write part of a line in one color and the rest in another, and so on. - """ - if indents and len(indents) != len(lines): - raise ValueError( - f"indents size ({len(indents)}) should have same size as lines ({len(lines)})" - ) - if not indents: - indents = [""] * len(lines) - source = "\n".join(lines) - new_lines = self._highlight(source).splitlines() - # Would be better to strict=True but that fails some CI jobs. - for indent, new_line in zip(indents, new_lines, strict=False): - self.line(indent + new_line) - - def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer: - if lexer == "python": - return PythonLexer() - elif lexer == "diff": - return DiffLexer() - else: - assert_never(lexer) - - def _get_pygments_formatter(self) -> TerminalFormatter: - from _pytest.config.exceptions import UsageError - - theme = os.getenv("PYTEST_THEME") - theme_mode = os.getenv("PYTEST_THEME_MODE", "dark") - - try: - return TerminalFormatter(bg=theme_mode, style=theme) - except pygments.util.ClassNotFound as e: - raise UsageError( - f"PYTEST_THEME environment variable has an invalid value: '{theme}'. " - "Hint: See available pygments styles with `pygmentize -L styles`." - ) from e - except pygments.util.OptionError as e: - raise UsageError( - f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. " - "The allowed values are 'dark' (default) and 'light'." - ) from e - - def _highlight( - self, source: str, lexer: Literal["diff", "python"] = "python" - ) -> str: - """Highlight the given source if we have markup support.""" - if not source or not self.hasmarkup or not self.code_highlight: - return source - - pygments_lexer = self._get_pygments_lexer(lexer) - pygments_formatter = self._get_pygments_formatter() - - highlighted: str = pygments.highlight( - source, pygments_lexer, pygments_formatter - ) - # pygments terminal formatter may add a newline when there wasn't one. - # We don't want this, remove. - if highlighted[-1] == "\n" and source[-1] != "\n": - highlighted = highlighted[:-1] - - # Some lexers will not set the initial color explicitly - # which may lead to the previous color being propagated to the - # start of the expression, so reset first. - highlighted = "\x1b[0m" + highlighted - - return highlighted diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/wcwidth.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_io/wcwidth.py deleted file mode 100644 index 23886ff..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_io/wcwidth.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -from functools import lru_cache -import unicodedata - - -@lru_cache(100) -def wcwidth(c: str) -> int: - """Determine how many columns are needed to display a character in a terminal. - - Returns -1 if the character is not printable. - Returns 0, 1 or 2 for other characters. - """ - o = ord(c) - - # ASCII fast path. - if 0x20 <= o < 0x07F: - return 1 - - # Some Cf/Zp/Zl characters which should be zero-width. - if ( - o == 0x0000 - or 0x200B <= o <= 0x200F - or 0x2028 <= o <= 0x202E - or 0x2060 <= o <= 0x2063 - ): - return 0 - - category = unicodedata.category(c) - - # Control characters. - if category == "Cc": - return -1 - - # Combining characters with zero width. - if category in ("Me", "Mn"): - return 0 - - # Full/Wide east asian characters. - if unicodedata.east_asian_width(c) in ("F", "W"): - return 2 - - return 1 - - -def wcswidth(s: str) -> int: - """Determine how many columns are needed to display a string in a terminal. - - Returns -1 if the string contains non-printable characters. - """ - width = 0 - for c in unicodedata.normalize("NFC", s): - wc = wcwidth(c) - if wc < 0: - return -1 - width += wc - return width diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index af09edd..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/error.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/error.cpython-311.pyc deleted file mode 100644 index 404ea70..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/error.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/path.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/path.cpython-311.pyc deleted file mode 100644 index 7073a4a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/__pycache__/path.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/error.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_py/error.py deleted file mode 100644 index dace237..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/error.py +++ /dev/null @@ -1,119 +0,0 @@ -"""create errno-specific classes for IO or os calls.""" - -from __future__ import annotations - -from collections.abc import Callable -import errno -import os -import sys -from typing import TYPE_CHECKING -from typing import TypeVar - - -if TYPE_CHECKING: - from typing_extensions import ParamSpec - - P = ParamSpec("P") - -R = TypeVar("R") - - -class Error(EnvironmentError): - def __repr__(self) -> str: - return "{}.{} {!r}: {} ".format( - self.__class__.__module__, - self.__class__.__name__, - self.__class__.__doc__, - " ".join(map(str, self.args)), - # repr(self.args) - ) - - def __str__(self) -> str: - s = "[{}]: {}".format( - self.__class__.__doc__, - " ".join(map(str, self.args)), - ) - return s - - -_winerrnomap = { - 2: errno.ENOENT, - 3: errno.ENOENT, - 17: errno.EEXIST, - 18: errno.EXDEV, - 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable - 22: errno.ENOTDIR, - 20: errno.ENOTDIR, - 267: errno.ENOTDIR, - 5: errno.EACCES, # anything better? -} - - -class ErrorMaker: - """lazily provides Exception classes for each possible POSIX errno - (as defined per the 'errno' module). All such instances - subclass EnvironmentError. - """ - - _errno2class: dict[int, type[Error]] = {} - - def __getattr__(self, name: str) -> type[Error]: - if name[0] == "_": - raise AttributeError(name) - eno = getattr(errno, name) - cls = self._geterrnoclass(eno) - setattr(self, name, cls) - return cls - - def _geterrnoclass(self, eno: int) -> type[Error]: - try: - return self._errno2class[eno] - except KeyError: - clsname = errno.errorcode.get(eno, f"UnknownErrno{eno}") - errorcls = type( - clsname, - (Error,), - {"__module__": "py.error", "__doc__": os.strerror(eno)}, - ) - self._errno2class[eno] = errorcls - return errorcls - - def checked_call( - self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs - ) -> R: - """Call a function and raise an errno-exception if applicable.""" - __tracebackhide__ = True - try: - return func(*args, **kwargs) - except Error: - raise - except OSError as value: - if not hasattr(value, "errno"): - raise - if sys.platform == "win32": - try: - # error: Invalid index type "Optional[int]" for "dict[int, int]"; expected type "int" [index] - # OK to ignore because we catch the KeyError below. - cls = self._geterrnoclass(_winerrnomap[value.errno]) # type:ignore[index] - except KeyError: - raise value - else: - # we are not on Windows, or we got a proper OSError - if value.errno is None: - cls = type( - "UnknownErrnoNone", - (Error,), - {"__module__": "py.error", "__doc__": None}, - ) - else: - cls = self._geterrnoclass(value.errno) - - raise cls(f"{func.__name__}{args!r}") - - -_error_maker = ErrorMaker() -checked_call = _error_maker.checked_call - - -def __getattr__(attr: str) -> type[Error]: - return getattr(_error_maker, attr) # type: ignore[no-any-return] diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/path.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_py/path.py deleted file mode 100644 index 998a781..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_py/path.py +++ /dev/null @@ -1,1475 +0,0 @@ -# mypy: allow-untyped-defs -"""local path implementation.""" - -from __future__ import annotations - -import atexit -from collections.abc import Callable -from contextlib import contextmanager -import fnmatch -import importlib.util -import io -import os -from os.path import abspath -from os.path import dirname -from os.path import exists -from os.path import isabs -from os.path import isdir -from os.path import isfile -from os.path import islink -from os.path import normpath -import posixpath -from stat import S_ISDIR -from stat import S_ISLNK -from stat import S_ISREG -import sys -from typing import Any -from typing import cast -from typing import Literal -from typing import overload -from typing import TYPE_CHECKING -import uuid -import warnings - -from . import error - - -# Moved from local.py. -iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") - - -class Checkers: - _depend_on_existence = "exists", "link", "dir", "file" - - def __init__(self, path): - self.path = path - - def dotfile(self): - return self.path.basename.startswith(".") - - def ext(self, arg): - if not arg.startswith("."): - arg = "." + arg - return self.path.ext == arg - - def basename(self, arg): - return self.path.basename == arg - - def basestarts(self, arg): - return self.path.basename.startswith(arg) - - def relto(self, arg): - return self.path.relto(arg) - - def fnmatch(self, arg): - return self.path.fnmatch(arg) - - def endswith(self, arg): - return str(self.path).endswith(arg) - - def _evaluate(self, kw): - from .._code.source import getrawcode - - for name, value in kw.items(): - invert = False - meth = None - try: - meth = getattr(self, name) - except AttributeError: - if name[:3] == "not": - invert = True - try: - meth = getattr(self, name[3:]) - except AttributeError: - pass - if meth is None: - raise TypeError(f"no {name!r} checker available for {self.path!r}") - try: - if getrawcode(meth).co_argcount > 1: - if (not meth(value)) ^ invert: - return False - else: - if bool(value) ^ bool(meth()) ^ invert: - return False - except (error.ENOENT, error.ENOTDIR, error.EBUSY): - # EBUSY feels not entirely correct, - # but its kind of necessary since ENOMEDIUM - # is not accessible in python - for name in self._depend_on_existence: - if name in kw: - if kw.get(name): - return False - name = "not" + name - if name in kw: - if not kw.get(name): - return False - return True - - _statcache: Stat - - def _stat(self) -> Stat: - try: - return self._statcache - except AttributeError: - try: - self._statcache = self.path.stat() - except error.ELOOP: - self._statcache = self.path.lstat() - return self._statcache - - def dir(self): - return S_ISDIR(self._stat().mode) - - def file(self): - return S_ISREG(self._stat().mode) - - def exists(self): - return self._stat() - - def link(self): - st = self.path.lstat() - return S_ISLNK(st.mode) - - -class NeverRaised(Exception): - pass - - -class Visitor: - def __init__(self, fil, rec, ignore, bf, sort): - if isinstance(fil, (str, bytes)): - fil = FNMatcher(fil) - if isinstance(rec, str): - self.rec: Callable[[LocalPath], bool] = FNMatcher(rec) - elif not hasattr(rec, "__call__") and rec: - self.rec = lambda path: True - else: - self.rec = rec - self.fil = fil - self.ignore = ignore - self.breadthfirst = bf - self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x) - - def gen(self, path): - try: - entries = path.listdir() - except self.ignore: - return - rec = self.rec - dirs = self.optsort( - [p for p in entries if p.check(dir=1) and (rec is None or rec(p))] - ) - if not self.breadthfirst: - for subdir in dirs: - yield from self.gen(subdir) - for p in self.optsort(entries): - if self.fil is None or self.fil(p): - yield p - if self.breadthfirst: - for subdir in dirs: - yield from self.gen(subdir) - - -class FNMatcher: - def __init__(self, pattern): - self.pattern = pattern - - def __call__(self, path): - pattern = self.pattern - - if ( - pattern.find(path.sep) == -1 - and iswin32 - and pattern.find(posixpath.sep) != -1 - ): - # Running on Windows, the pattern has no Windows path separators, - # and the pattern has one or more Posix path separators. Replace - # the Posix path separators with the Windows path separator. - pattern = pattern.replace(posixpath.sep, path.sep) - - if pattern.find(path.sep) == -1: - name = path.basename - else: - name = str(path) # path.strpath # XXX svn? - if not os.path.isabs(pattern): - pattern = "*" + path.sep + pattern - return fnmatch.fnmatch(name, pattern) - - -def map_as_list(func, iter): - return list(map(func, iter)) - - -class Stat: - if TYPE_CHECKING: - - @property - def size(self) -> int: ... - - @property - def mtime(self) -> float: ... - - def __getattr__(self, name: str) -> Any: - return getattr(self._osstatresult, "st_" + name) - - def __init__(self, path, osstatresult): - self.path = path - self._osstatresult = osstatresult - - @property - def owner(self): - if iswin32: - raise NotImplementedError("XXX win32") - import pwd - - entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore] - return entry[0] - - @property - def group(self): - """Return group name of file.""" - if iswin32: - raise NotImplementedError("XXX win32") - import grp - - entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore] - return entry[0] - - def isdir(self): - return S_ISDIR(self._osstatresult.st_mode) - - def isfile(self): - return S_ISREG(self._osstatresult.st_mode) - - def islink(self): - self.path.lstat() - return S_ISLNK(self._osstatresult.st_mode) - - -def getuserid(user): - import pwd - - if not isinstance(user, int): - user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore] - return user - - -def getgroupid(group): - import grp - - if not isinstance(group, int): - group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore] - return group - - -class LocalPath: - """Object oriented interface to os.path and other local filesystem - related information. - """ - - class ImportMismatchError(ImportError): - """raised on pyimport() if there is a mismatch of __file__'s""" - - sep = os.sep - - def __init__(self, path=None, expanduser=False): - """Initialize and return a local Path instance. - - Path can be relative to the current directory. - If path is None it defaults to the current working directory. - If expanduser is True, tilde-expansion is performed. - Note that Path instances always carry an absolute path. - Note also that passing in a local path object will simply return - the exact same path object. Use new() to get a new copy. - """ - if path is None: - self.strpath = error.checked_call(os.getcwd) - else: - try: - path = os.fspath(path) - except TypeError: - raise ValueError( - "can only pass None, Path instances " - "or non-empty strings to LocalPath" - ) - if expanduser: - path = os.path.expanduser(path) - self.strpath = abspath(path) - - if sys.platform != "win32": - - def chown(self, user, group, rec=0): - """Change ownership to the given user and group. - user and group may be specified by a number or - by a name. if rec is True change ownership - recursively. - """ - uid = getuserid(user) - gid = getgroupid(group) - if rec: - for x in self.visit(rec=lambda x: x.check(link=0)): - if x.check(link=0): - error.checked_call(os.chown, str(x), uid, gid) - error.checked_call(os.chown, str(self), uid, gid) - - def readlink(self) -> str: - """Return value of a symbolic link.""" - # https://github.com/python/mypy/issues/12278 - return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value,unused-ignore] - - def mklinkto(self, oldname): - """Posix style hard link to another name.""" - error.checked_call(os.link, str(oldname), str(self)) - - def mksymlinkto(self, value, absolute=1): - """Create a symbolic link with the given value (pointing to another name).""" - if absolute: - error.checked_call(os.symlink, str(value), self.strpath) - else: - base = self.common(value) - # with posix local paths '/' is always a common base - relsource = self.__class__(value).relto(base) - reldest = self.relto(base) - n = reldest.count(self.sep) - target = self.sep.join(("..",) * n + (relsource,)) - error.checked_call(os.symlink, target, self.strpath) - - def __div__(self, other): - return self.join(os.fspath(other)) - - __truediv__ = __div__ # py3k - - @property - def basename(self): - """Basename part of path.""" - return self._getbyspec("basename")[0] - - @property - def dirname(self): - """Dirname part of path.""" - return self._getbyspec("dirname")[0] - - @property - def purebasename(self): - """Pure base name of the path.""" - return self._getbyspec("purebasename")[0] - - @property - def ext(self): - """Extension of the path (including the '.').""" - return self._getbyspec("ext")[0] - - def read_binary(self): - """Read and return a bytestring from reading the path.""" - with self.open("rb") as f: - return f.read() - - def read_text(self, encoding): - """Read and return a Unicode string from reading the path.""" - with self.open("r", encoding=encoding) as f: - return f.read() - - def read(self, mode="r"): - """Read and return a bytestring from reading the path.""" - with self.open(mode) as f: - return f.read() - - def readlines(self, cr=1): - """Read and return a list of lines from the path. if cr is False, the - newline will be removed from the end of each line.""" - mode = "r" - - if not cr: - content = self.read(mode) - return content.split("\n") - else: - f = self.open(mode) - try: - return f.readlines() - finally: - f.close() - - def load(self): - """(deprecated) return object unpickled from self.read()""" - f = self.open("rb") - try: - import pickle - - return error.checked_call(pickle.load, f) - finally: - f.close() - - def move(self, target): - """Move this path to target.""" - if target.relto(self): - raise error.EINVAL(target, "cannot move path into a subdirectory of itself") - try: - self.rename(target) - except error.EXDEV: # invalid cross-device link - self.copy(target) - self.remove() - - def fnmatch(self, pattern): - """Return true if the basename/fullname matches the glob-'pattern'. - - valid pattern characters:: - - * matches everything - ? matches any single character - [seq] matches any character in seq - [!seq] matches any char not in seq - - If the pattern contains a path-separator then the full path - is used for pattern matching and a '*' is prepended to the - pattern. - - if the pattern doesn't contain a path-separator the pattern - is only matched against the basename. - """ - return FNMatcher(pattern)(self) - - def relto(self, relpath): - """Return a string which is the relative part of the path - to the given 'relpath'. - """ - if not isinstance(relpath, str | LocalPath): - raise TypeError(f"{relpath!r}: not a string or path object") - strrelpath = str(relpath) - if strrelpath and strrelpath[-1] != self.sep: - strrelpath += self.sep - # assert strrelpath[-1] == self.sep - # assert strrelpath[-2] != self.sep - strself = self.strpath - if sys.platform == "win32" or getattr(os, "_name", None) == "nt": - if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)): - return strself[len(strrelpath) :] - elif strself.startswith(strrelpath): - return strself[len(strrelpath) :] - return "" - - def ensure_dir(self, *args): - """Ensure the path joined with args is a directory.""" - return self.ensure(*args, dir=True) - - def bestrelpath(self, dest): - """Return a string which is a relative path from self - (assumed to be a directory) to dest such that - self.join(bestrelpath) == dest and if not such - path can be determined return dest. - """ - try: - if self == dest: - return os.curdir - base = self.common(dest) - if not base: # can be the case on windows - return str(dest) - self2base = self.relto(base) - reldest = dest.relto(base) - if self2base: - n = self2base.count(self.sep) + 1 - else: - n = 0 - lst = [os.pardir] * n - if reldest: - lst.append(reldest) - target = dest.sep.join(lst) - return target - except AttributeError: - return str(dest) - - def exists(self): - return self.check() - - def isdir(self): - return self.check(dir=1) - - def isfile(self): - return self.check(file=1) - - def parts(self, reverse=False): - """Return a root-first list of all ancestor directories - plus the path itself. - """ - current = self - lst = [self] - while 1: - last = current - current = current.dirpath() - if last == current: - break - lst.append(current) - if not reverse: - lst.reverse() - return lst - - def common(self, other): - """Return the common part shared with the other path - or None if there is no common part. - """ - last = None - for x, y in zip(self.parts(), other.parts()): - if x != y: - return last - last = x - return last - - def __add__(self, other): - """Return new path object with 'other' added to the basename""" - return self.new(basename=self.basename + str(other)) - - def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): - """Yields all paths below the current one - - fil is a filter (glob pattern or callable), if not matching the - path will not be yielded, defaulting to None (everything is - returned) - - rec is a filter (glob pattern or callable) that controls whether - a node is descended, defaulting to None - - ignore is an Exception class that is ignoredwhen calling dirlist() - on any of the paths (by default, all exceptions are reported) - - bf if True will cause a breadthfirst search instead of the - default depthfirst. Default: False - - sort if True will sort entries within each directory level. - """ - yield from Visitor(fil, rec, ignore, bf, sort).gen(self) - - def _sortlist(self, res, sort): - if sort: - if hasattr(sort, "__call__"): - warnings.warn( - DeprecationWarning( - "listdir(sort=callable) is deprecated and breaks on python3" - ), - stacklevel=3, - ) - res.sort(sort) - else: - res.sort() - - def __fspath__(self): - return self.strpath - - def __hash__(self): - s = self.strpath - if iswin32: - s = s.lower() - return hash(s) - - def __eq__(self, other): - s1 = os.fspath(self) - try: - s2 = os.fspath(other) - except TypeError: - return False - if iswin32: - s1 = s1.lower() - try: - s2 = s2.lower() - except AttributeError: - return False - return s1 == s2 - - def __ne__(self, other): - return not (self == other) - - def __lt__(self, other): - return os.fspath(self) < os.fspath(other) - - def __gt__(self, other): - return os.fspath(self) > os.fspath(other) - - def samefile(self, other): - """Return True if 'other' references the same file as 'self'.""" - other = os.fspath(other) - if not isabs(other): - other = abspath(other) - if self == other: - return True - if not hasattr(os.path, "samefile"): - return False - return error.checked_call(os.path.samefile, self.strpath, other) - - def remove(self, rec=1, ignore_errors=False): - """Remove a file or directory (or a directory tree if rec=1). - if ignore_errors is True, errors while removing directories will - be ignored. - """ - if self.check(dir=1, link=0): - if rec: - # force remove of readonly files on windows - if iswin32: - self.chmod(0o700, rec=1) - import shutil - - error.checked_call( - shutil.rmtree, self.strpath, ignore_errors=ignore_errors - ) - else: - error.checked_call(os.rmdir, self.strpath) - else: - if iswin32: - self.chmod(0o700) - error.checked_call(os.remove, self.strpath) - - def computehash(self, hashtype="md5", chunksize=524288): - """Return hexdigest of hashvalue for this file.""" - try: - try: - import hashlib as mod - except ImportError: - if hashtype == "sha1": - hashtype = "sha" - mod = __import__(hashtype) - hash = getattr(mod, hashtype)() - except (AttributeError, ImportError): - raise ValueError(f"Don't know how to compute {hashtype!r} hash") - f = self.open("rb") - try: - while 1: - buf = f.read(chunksize) - if not buf: - return hash.hexdigest() - hash.update(buf) - finally: - f.close() - - def new(self, **kw): - """Create a modified version of this path. - the following keyword arguments modify various path parts:: - - a:/some/path/to/a/file.ext - xx drive - xxxxxxxxxxxxxxxxx dirname - xxxxxxxx basename - xxxx purebasename - xxx ext - """ - obj = object.__new__(self.__class__) - if not kw: - obj.strpath = self.strpath - return obj - drive, dirname, _basename, purebasename, ext = self._getbyspec( - "drive,dirname,basename,purebasename,ext" - ) - if "basename" in kw: - if "purebasename" in kw or "ext" in kw: - raise ValueError(f"invalid specification {kw!r}") - else: - pb = kw.setdefault("purebasename", purebasename) - try: - ext = kw["ext"] - except KeyError: - pass - else: - if ext and not ext.startswith("."): - ext = "." + ext - kw["basename"] = pb + ext - - if "dirname" in kw and not kw["dirname"]: - kw["dirname"] = drive - else: - kw.setdefault("dirname", dirname) - kw.setdefault("sep", self.sep) - obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw)) - return obj - - def _getbyspec(self, spec: str) -> list[str]: - """See new for what 'spec' can be.""" - res = [] - parts = self.strpath.split(self.sep) - - args = filter(None, spec.split(",")) - for name in args: - if name == "drive": - res.append(parts[0]) - elif name == "dirname": - res.append(self.sep.join(parts[:-1])) - else: - basename = parts[-1] - if name == "basename": - res.append(basename) - else: - i = basename.rfind(".") - if i == -1: - purebasename, ext = basename, "" - else: - purebasename, ext = basename[:i], basename[i:] - if name == "purebasename": - res.append(purebasename) - elif name == "ext": - res.append(ext) - else: - raise ValueError(f"invalid part specification {name!r}") - return res - - def dirpath(self, *args, **kwargs): - """Return the directory path joined with any given path arguments.""" - if not kwargs: - path = object.__new__(self.__class__) - path.strpath = dirname(self.strpath) - if args: - path = path.join(*args) - return path - return self.new(basename="").join(*args, **kwargs) - - def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath: - """Return a new path by appending all 'args' as path - components. if abs=1 is used restart from root if any - of the args is an absolute path. - """ - sep = self.sep - strargs = [os.fspath(arg) for arg in args] - strpath = self.strpath - if abs: - newargs: list[str] = [] - for arg in reversed(strargs): - if isabs(arg): - strpath = arg - strargs = newargs - break - newargs.insert(0, arg) - # special case for when we have e.g. strpath == "/" - actual_sep = "" if strpath.endswith(sep) else sep - for arg in strargs: - arg = arg.strip(sep) - if iswin32: - # allow unix style paths even on windows. - arg = arg.strip("/") - arg = arg.replace("/", sep) - strpath = strpath + actual_sep + arg - actual_sep = sep - obj = object.__new__(self.__class__) - obj.strpath = normpath(strpath) - return obj - - def open(self, mode="r", ensure=False, encoding=None): - """Return an opened file with the given mode. - - If ensure is True, create parent directories if needed. - """ - if ensure: - self.dirpath().ensure(dir=1) - if encoding: - return error.checked_call( - io.open, - self.strpath, - mode, - encoding=encoding, - ) - return error.checked_call(open, self.strpath, mode) - - def _fastjoin(self, name): - child = object.__new__(self.__class__) - child.strpath = self.strpath + self.sep + name - return child - - def islink(self): - return islink(self.strpath) - - def check(self, **kw): - """Check a path for existence and properties. - - Without arguments, return True if the path exists, otherwise False. - - valid checkers:: - - file = 1 # is a file - file = 0 # is not a file (may not even exist) - dir = 1 # is a dir - link = 1 # is a link - exists = 1 # exists - - You can specify multiple checker definitions, for example:: - - path.check(file=1, link=1) # a link pointing to a file - """ - if not kw: - return exists(self.strpath) - if len(kw) == 1: - if "dir" in kw: - return not kw["dir"] ^ isdir(self.strpath) - if "file" in kw: - return not kw["file"] ^ isfile(self.strpath) - if not kw: - kw = {"exists": 1} - return Checkers(self)._evaluate(kw) - - _patternchars = set("*?[" + os.sep) - - def listdir(self, fil=None, sort=None): - """List directory contents, possibly filter by the given fil func - and possibly sorted. - """ - if fil is None and sort is None: - names = error.checked_call(os.listdir, self.strpath) - return map_as_list(self._fastjoin, names) - if isinstance(fil, str): - if not self._patternchars.intersection(fil): - child = self._fastjoin(fil) - if exists(child.strpath): - return [child] - return [] - fil = FNMatcher(fil) - names = error.checked_call(os.listdir, self.strpath) - res = [] - for name in names: - child = self._fastjoin(name) - if fil is None or fil(child): - res.append(child) - self._sortlist(res, sort) - return res - - def size(self) -> int: - """Return size of the underlying file object""" - return self.stat().size - - def mtime(self) -> float: - """Return last modification time of the path.""" - return self.stat().mtime - - def copy(self, target, mode=False, stat=False): - """Copy path to target. - - If mode is True, will copy permission from path to target. - If stat is True, copy permission, last modification - time, last access time, and flags from path to target. - """ - if self.check(file=1): - if target.check(dir=1): - target = target.join(self.basename) - assert self != target - copychunked(self, target) - if mode: - copymode(self.strpath, target.strpath) - if stat: - copystat(self, target) - else: - - def rec(p): - return p.check(link=0) - - for x in self.visit(rec=rec): - relpath = x.relto(self) - newx = target.join(relpath) - newx.dirpath().ensure(dir=1) - if x.check(link=1): - newx.mksymlinkto(x.readlink()) - continue - elif x.check(file=1): - copychunked(x, newx) - elif x.check(dir=1): - newx.ensure(dir=1) - if mode: - copymode(x.strpath, newx.strpath) - if stat: - copystat(x, newx) - - def rename(self, target): - """Rename this path to target.""" - target = os.fspath(target) - return error.checked_call(os.rename, self.strpath, target) - - def dump(self, obj, bin=1): - """Pickle object into path location""" - f = self.open("wb") - import pickle - - try: - error.checked_call(pickle.dump, obj, f, bin) - finally: - f.close() - - def mkdir(self, *args): - """Create & return the directory joined with args.""" - p = self.join(*args) - error.checked_call(os.mkdir, os.fspath(p)) - return p - - def write_binary(self, data, ensure=False): - """Write binary data into path. If ensure is True create - missing parent directories. - """ - if ensure: - self.dirpath().ensure(dir=1) - with self.open("wb") as f: - f.write(data) - - def write_text(self, data, encoding, ensure=False): - """Write text data into path using the specified encoding. - If ensure is True create missing parent directories. - """ - if ensure: - self.dirpath().ensure(dir=1) - with self.open("w", encoding=encoding) as f: - f.write(data) - - def write(self, data, mode="w", ensure=False): - """Write data into path. If ensure is True create - missing parent directories. - """ - if ensure: - self.dirpath().ensure(dir=1) - if "b" in mode: - if not isinstance(data, bytes): - raise ValueError("can only process bytes") - else: - if not isinstance(data, str): - if not isinstance(data, bytes): - data = str(data) - else: - data = data.decode(sys.getdefaultencoding()) - f = self.open(mode) - try: - f.write(data) - finally: - f.close() - - def _ensuredirs(self): - parent = self.dirpath() - if parent == self: - return self - if parent.check(dir=0): - parent._ensuredirs() - if self.check(dir=0): - try: - self.mkdir() - except error.EEXIST: - # race condition: file/dir created by another thread/process. - # complain if it is not a dir - if self.check(dir=0): - raise - return self - - def ensure(self, *args, **kwargs): - """Ensure that an args-joined path exists (by default as - a file). if you specify a keyword argument 'dir=True' - then the path is forced to be a directory path. - """ - p = self.join(*args) - if kwargs.get("dir", 0): - return p._ensuredirs() - else: - p.dirpath()._ensuredirs() - if not p.check(file=1): - p.open("wb").close() - return p - - @overload - def stat(self, raising: Literal[True] = ...) -> Stat: ... - - @overload - def stat(self, raising: Literal[False]) -> Stat | None: ... - - def stat(self, raising: bool = True) -> Stat | None: - """Return an os.stat() tuple.""" - if raising: - return Stat(self, error.checked_call(os.stat, self.strpath)) - try: - return Stat(self, os.stat(self.strpath)) - except KeyboardInterrupt: - raise - except Exception: - return None - - def lstat(self) -> Stat: - """Return an os.lstat() tuple.""" - return Stat(self, error.checked_call(os.lstat, self.strpath)) - - def setmtime(self, mtime=None): - """Set modification time for the given path. if 'mtime' is None - (the default) then the file's mtime is set to current time. - - Note that the resolution for 'mtime' is platform dependent. - """ - if mtime is None: - return error.checked_call(os.utime, self.strpath, mtime) - try: - return error.checked_call(os.utime, self.strpath, (-1, mtime)) - except error.EINVAL: - return error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) - - def chdir(self): - """Change directory to self and return old current directory""" - try: - old = self.__class__() - except error.ENOENT: - old = None - error.checked_call(os.chdir, self.strpath) - return old - - @contextmanager - def as_cwd(self): - """ - Return a context manager, which changes to the path's dir during the - managed "with" context. - On __enter__ it returns the old dir, which might be ``None``. - """ - old = self.chdir() - try: - yield old - finally: - if old is not None: - old.chdir() - - def realpath(self): - """Return a new path which contains no symbolic links.""" - return self.__class__(os.path.realpath(self.strpath)) - - def atime(self): - """Return last access time of the path.""" - return self.stat().atime - - def __repr__(self): - return f"local({self.strpath!r})" - - def __str__(self): - """Return string representation of the Path.""" - return self.strpath - - def chmod(self, mode, rec=0): - """Change permissions to the given mode. If mode is an - integer it directly encodes the os-specific modes. - if rec is True perform recursively. - """ - if not isinstance(mode, int): - raise TypeError(f"mode {mode!r} must be an integer") - if rec: - for x in self.visit(rec=rec): - error.checked_call(os.chmod, str(x), mode) - error.checked_call(os.chmod, self.strpath, mode) - - def pypkgpath(self): - """Return the Python package path by looking for the last - directory upwards which still contains an __init__.py. - Return None if a pkgpath cannot be determined. - """ - pkgpath = None - for parent in self.parts(reverse=True): - if parent.isdir(): - if not parent.join("__init__.py").exists(): - break - if not isimportable(parent.basename): - break - pkgpath = parent - return pkgpath - - def _ensuresyspath(self, ensuremode, path): - if ensuremode: - s = str(path) - if ensuremode == "append": - if s not in sys.path: - sys.path.append(s) - else: - if s != sys.path[0]: - sys.path.insert(0, s) - - def pyimport(self, modname=None, ensuresyspath=True): - """Return path as an imported python module. - - If modname is None, look for the containing package - and construct an according module name. - The module will be put/looked up in sys.modules. - if ensuresyspath is True then the root dir for importing - the file (taking __init__.py files into account) will - be prepended to sys.path if it isn't there already. - If ensuresyspath=="append" the root dir will be appended - if it isn't already contained in sys.path. - if ensuresyspath is False no modification of syspath happens. - - Special value of ensuresyspath=="importlib" is intended - purely for using in pytest, it is capable only of importing - separate .py files outside packages, e.g. for test suite - without any __init__.py file. It effectively allows having - same-named test modules in different places and offers - mild opt-in via this option. Note that it works only in - recent versions of python. - """ - if not self.check(): - raise error.ENOENT(self) - - if ensuresyspath == "importlib": - if modname is None: - modname = self.purebasename - spec = importlib.util.spec_from_file_location(modname, str(self)) - if spec is None or spec.loader is None: - raise ImportError(f"Can't find module {modname} at location {self!s}") - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - return mod - - pkgpath = None - if modname is None: - pkgpath = self.pypkgpath() - if pkgpath is not None: - pkgroot = pkgpath.dirpath() - names = self.new(ext="").relto(pkgroot).split(self.sep) - if names[-1] == "__init__": - names.pop() - modname = ".".join(names) - else: - pkgroot = self.dirpath() - modname = self.purebasename - - self._ensuresyspath(ensuresyspath, pkgroot) - __import__(modname) - mod = sys.modules[modname] - if self.basename == "__init__.py": - return mod # we don't check anything as we might - # be in a namespace package ... too icky to check - modfile = mod.__file__ - assert modfile is not None - if modfile[-4:] in (".pyc", ".pyo"): - modfile = modfile[:-1] - elif modfile.endswith("$py.class"): - modfile = modfile[:-9] + ".py" - if modfile.endswith(os.sep + "__init__.py"): - if self.basename != "__init__.py": - modfile = modfile[:-12] - try: - issame = self.samefile(modfile) - except error.ENOENT: - issame = False - if not issame: - ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH") - if ignore != "1": - raise self.ImportMismatchError(modname, modfile, self) - return mod - else: - try: - return sys.modules[modname] - except KeyError: - # we have a custom modname, do a pseudo-import - import types - - mod = types.ModuleType(modname) - mod.__file__ = str(self) - sys.modules[modname] = mod - try: - with open(str(self), "rb") as f: - exec(f.read(), mod.__dict__) - except BaseException: - del sys.modules[modname] - raise - return mod - - def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str: - """Return stdout text from executing a system child process, - where the 'self' path points to executable. - The process is directly invoked and not through a system shell. - """ - from subprocess import PIPE - from subprocess import Popen - - popen_opts.pop("stdout", None) - popen_opts.pop("stderr", None) - proc = Popen( - [str(self)] + [str(arg) for arg in argv], - **popen_opts, - stdout=PIPE, - stderr=PIPE, - ) - stdout: str | bytes - stdout, stderr = proc.communicate() - ret = proc.wait() - if isinstance(stdout, bytes): - stdout = stdout.decode(sys.getdefaultencoding()) - if ret != 0: - if isinstance(stderr, bytes): - stderr = stderr.decode(sys.getdefaultencoding()) - raise RuntimeError( - ret, - ret, - str(self), - stdout, - stderr, - ) - return stdout - - @classmethod - def sysfind(cls, name, checker=None, paths=None): - """Return a path object found by looking at the systems - underlying PATH specification. If the checker is not None - it will be invoked to filter matching paths. If a binary - cannot be found, None is returned - Note: This is probably not working on plain win32 systems - but may work on cygwin. - """ - if isabs(name): - p = local(name) - if p.check(file=1): - return p - else: - if paths is None: - if iswin32: - paths = os.environ["Path"].split(";") - if "" not in paths and "." not in paths: - paths.append(".") - try: - systemroot = os.environ["SYSTEMROOT"] - except KeyError: - pass - else: - paths = [ - path.replace("%SystemRoot%", systemroot) for path in paths - ] - else: - paths = os.environ["PATH"].split(":") - tryadd = [] - if iswin32: - tryadd += os.environ["PATHEXT"].split(os.pathsep) - tryadd.append("") - - for x in paths: - for addext in tryadd: - p = local(x).join(name, abs=True) + addext - try: - if p.check(file=1): - if checker: - if not checker(p): - continue - return p - except error.EACCES: - pass - return None - - @classmethod - def _gethomedir(cls): - try: - x = os.environ["HOME"] - except KeyError: - try: - x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"] - except KeyError: - return None - return cls(x) - - # """ - # special class constructors for local filesystem paths - # """ - @classmethod - def get_temproot(cls): - """Return the system's temporary directory - (where tempfiles are usually created in) - """ - import tempfile - - return local(tempfile.gettempdir()) - - @classmethod - def mkdtemp(cls, rootdir=None): - """Return a Path object pointing to a fresh new temporary directory - (which we created ourselves). - """ - import tempfile - - if rootdir is None: - rootdir = cls.get_temproot() - path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir)) - return cls(path) - - @classmethod - def make_numbered_dir( - cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800 - ): # two days - """Return unique directory with a number greater than the current - maximum one. The number is assumed to start directly after prefix. - if keep is true directories with a number less than (maxnum-keep) - will be removed. If .lock files are used (lock_timeout non-zero), - algorithm is multi-process safe. - """ - if rootdir is None: - rootdir = cls.get_temproot() - - nprefix = prefix.lower() - - def parse_num(path): - """Parse the number out of a path (if it matches the prefix)""" - nbasename = path.basename.lower() - if nbasename.startswith(nprefix): - try: - return int(nbasename[len(nprefix) :]) - except ValueError: - pass - - def create_lockfile(path): - """Exclusively create lockfile. Throws when failed""" - mypid = os.getpid() - lockfile = path.join(".lock") - if hasattr(lockfile, "mksymlinkto"): - lockfile.mksymlinkto(str(mypid)) - else: - fd = error.checked_call( - os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644 - ) - with os.fdopen(fd, "w") as f: - f.write(str(mypid)) - return lockfile - - def atexit_remove_lockfile(lockfile): - """Ensure lockfile is removed at process exit""" - mypid = os.getpid() - - def try_remove_lockfile(): - # in a fork() situation, only the last process should - # remove the .lock, otherwise the other processes run the - # risk of seeing their temporary dir disappear. For now - # we remove the .lock in the parent only (i.e. we assume - # that the children finish before the parent). - if os.getpid() != mypid: - return - try: - lockfile.remove() - except error.Error: - pass - - atexit.register(try_remove_lockfile) - - # compute the maximum number currently in use with the prefix - lastmax = None - while True: - maxnum = -1 - for path in rootdir.listdir(): - num = parse_num(path) - if num is not None: - maxnum = max(maxnum, num) - - # make the new directory - try: - udir = rootdir.mkdir(prefix + str(maxnum + 1)) - if lock_timeout: - lockfile = create_lockfile(udir) - atexit_remove_lockfile(lockfile) - except (error.EEXIST, error.ENOENT, error.EBUSY): - # race condition (1): another thread/process created the dir - # in the meantime - try again - # race condition (2): another thread/process spuriously acquired - # lock treating empty directory as candidate - # for removal - try again - # race condition (3): another thread/process tried to create the lock at - # the same time (happened in Python 3.3 on Windows) - # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa - if lastmax == maxnum: - raise - lastmax = maxnum - continue - break - - def get_mtime(path): - """Read file modification time""" - try: - return path.lstat().mtime - except error.Error: - pass - - garbage_prefix = prefix + "garbage-" - - def is_garbage(path): - """Check if path denotes directory scheduled for removal""" - bn = path.basename - return bn.startswith(garbage_prefix) - - # prune old directories - udir_time = get_mtime(udir) - if keep and udir_time: - for path in rootdir.listdir(): - num = parse_num(path) - if num is not None and num <= (maxnum - keep): - try: - # try acquiring lock to remove directory as exclusive user - if lock_timeout: - create_lockfile(path) - except (error.EEXIST, error.ENOENT, error.EBUSY): - path_time = get_mtime(path) - if not path_time: - # assume directory doesn't exist now - continue - if abs(udir_time - path_time) < lock_timeout: - # assume directory with lockfile exists - # and lock timeout hasn't expired yet - continue - - # path dir locked for exclusive use - # and scheduled for removal to avoid another thread/process - # treating it as a new directory or removal candidate - garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4())) - try: - path.rename(garbage_path) - garbage_path.remove(rec=1) - except KeyboardInterrupt: - raise - except Exception: # this might be error.Error, WindowsError ... - pass - if is_garbage(path): - try: - path.remove(rec=1) - except KeyboardInterrupt: - raise - except Exception: # this might be error.Error, WindowsError ... - pass - - # make link... - try: - username = os.environ["USER"] # linux, et al - except KeyError: - try: - username = os.environ["USERNAME"] # windows - except KeyError: - username = "current" - - src = str(udir) - dest = src[: src.rfind("-")] + "-" + username - try: - os.unlink(dest) - except OSError: - pass - try: - os.symlink(src, dest) - except (OSError, AttributeError, NotImplementedError): - pass - - return udir - - -def copymode(src, dest): - """Copy permission from src to dst.""" - import shutil - - shutil.copymode(src, dest) - - -def copystat(src, dest): - """Copy permission, last modification time, - last access time, and flags from src to dst.""" - import shutil - - shutil.copystat(str(src), str(dest)) - - -def copychunked(src, dest): - chunksize = 524288 # half a meg of bytes - fsrc = src.open("rb") - try: - fdest = dest.open("wb") - try: - while 1: - buf = fsrc.read(chunksize) - if not buf: - break - fdest.write(buf) - finally: - fdest.close() - finally: - fsrc.close() - - -def isimportable(name): - if name and (name[0].isalpha() or name[0] == "_"): - name = name.replace("_", "") - return not name or name.isalnum() - - -local = LocalPath diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/_version.py b/tests/venv2/lib/python3.11/site-packages/_pytest/_version.py deleted file mode 100644 index faf5c55..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/_version.py +++ /dev/null @@ -1,24 +0,0 @@ -# file generated by vcs-versioning -# don't change, don't track in version control -from __future__ import annotations - -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", - "__commit_id__", - "commit_id", -] - -version: str -__version__: str -__version_tuple__: tuple[int | str, ...] -version_tuple: tuple[int | str, ...] -commit_id: str | None -__commit_id__: str | None - -__version__ = version = '9.1.1' -__version_tuple__ = version_tuple = (9, 1, 1) - -__commit_id__ = commit_id = None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__init__.py deleted file mode 100644 index e33f8b2..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__init__.py +++ /dev/null @@ -1,236 +0,0 @@ -# mypy: allow-untyped-defs -"""Support for presenting detailed information in failing assertions.""" - -from __future__ import annotations - -from collections.abc import Generator -import sys -from typing import Any -from typing import Protocol -from typing import TYPE_CHECKING - -from _pytest.assertion import rewrite -from _pytest.assertion import truncate -from _pytest.assertion import util -from _pytest.assertion.rewrite import assertstate_key -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.nodes import Item - - -if TYPE_CHECKING: - from _pytest.main import Session - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("debugconfig") - group.addoption( - "--assert", - action="store", - dest="assertmode", - choices=("rewrite", "plain"), - default="rewrite", - metavar="MODE", - help=( - "Control assertion debugging tools.\n" - "'plain' performs no assertion debugging.\n" - "'rewrite' (the default) rewrites assert statements in test modules" - " on import to provide assert expression information." - ), - ) - parser.addini( - "enable_assertion_pass_hook", - type="bool", - default=False, - help="Enables the pytest_assertion_pass hook. " - "Make sure to delete any previously generated pyc cache files.", - ) - - parser.addini( - "truncation_limit_lines", - default=None, - help="Set threshold of LINES after which truncation will take effect", - ) - parser.addini( - "truncation_limit_chars", - default=None, - help=("Set threshold of CHARS after which truncation will take effect"), - ) - parser.addini( - "assertion_text_diff_style", - default=util.ASSERTION_TEXT_DIFF_STYLE_NDIFF, - help=( - "Choose how pytest renders diffs for string equality assertions: " - f"{util.ASSERTION_TEXT_DIFF_STYLE_NDIFF} or " - f"{util.ASSERTION_TEXT_DIFF_STYLE_BLOCK}" - ), - ) - - Config._add_verbosity_ini( - parser, - Config.VERBOSITY_ASSERTIONS, - help=( - "Specify a verbosity level for assertions, overriding the main level. " - "Higher levels will provide more detailed explanation when an assertion fails." - ), - ) - - -def pytest_configure(config: Config) -> None: - util.validate_assertion_text_diff_style(config) - - -def register_assert_rewrite(*names: str) -> None: - """Register one or more module names to be rewritten on import. - - This function will make sure that this module or all modules inside - the package will get their assert statements rewritten. - Thus you should make sure to call this before the module is - actually imported, usually in your __init__.py if you are a plugin - using a package. - - :param names: The module names to register. - """ - for name in names: - if not isinstance(name, str): - msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] - raise TypeError(msg.format(repr(names))) - rewrite_hook: RewriteHook - for hook in sys.meta_path: - if isinstance(hook, rewrite.AssertionRewritingHook): - rewrite_hook = hook - break - else: - rewrite_hook = DummyRewriteHook() - rewrite_hook.mark_rewrite(*names) - - -class RewriteHook(Protocol): - def mark_rewrite(self, *names: str) -> None: ... - - -class DummyRewriteHook: - """A no-op import hook for when rewriting is disabled.""" - - def mark_rewrite(self, *names: str) -> None: - pass - - -class AssertionState: - """State for the assertion plugin.""" - - def __init__(self, config: Config, mode) -> None: - self.mode = mode - self.trace = config.trace.root.get("assertion") - self.hook: rewrite.AssertionRewritingHook | None = None - - -def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: - """Try to install the rewrite hook, raise SystemError if it fails.""" - config.stash[assertstate_key] = AssertionState(config, "rewrite") - config.stash[assertstate_key].hook = hook = rewrite.AssertionRewritingHook(config) - sys.meta_path.insert(0, hook) - config.stash[assertstate_key].trace("installed rewrite import hook") - - def undo() -> None: - hook = config.stash[assertstate_key].hook - if hook is not None and hook in sys.meta_path: - sys.meta_path.remove(hook) - - config.add_cleanup(undo) - return hook - - -def pytest_collection(session: Session) -> None: - # This hook is only called when test modules are collected - # so for example not in the managing process of pytest-xdist - # (which does not collect test modules). - assertstate = session.config.stash.get(assertstate_key, None) - if assertstate: - if assertstate.hook is not None: - assertstate.hook.set_session(session) - - -@hookimpl(wrapper=True, tryfirst=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: - """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks. - - The rewrite module will use util._reprcompare if it exists to use custom - reporting via the pytest_assertrepr_compare hook. This sets up this custom - comparison for the test. - """ - ihook = item.ihook - - def callbinrepr(op, left: object, right: object) -> str | None: - """Call the pytest_assertrepr_compare hook and prepare the result. - - This uses the first result from the hook and then ensures the - following: - * Overly verbose explanations are truncated unless configured otherwise - (eg. if running in verbose mode). - * Embedded newlines are escaped to help util.format_explanation() - later. - * If the rewrite mode is used embedded %-characters are replaced - to protect later % formatting. - - The result can be formatted by util.format_explanation() for - pretty printing. - """ - hook_result = ihook.pytest_assertrepr_compare( - config=item.config, op=op, left=left, right=right - ) - for new_expl in hook_result: - if new_expl: - new_expl = truncate.truncate_if_required(new_expl, item) - new_expl = [line.replace("\n", "\\n") for line in new_expl] - res = "\n~".join(new_expl) - if item.config.getvalue("assertmode") == "rewrite": - res = res.replace("%", "%%") - return res - return None - - saved_assert_hooks = util._reprcompare, util._assertion_pass - util._reprcompare = callbinrepr - util._config = item.config - - if ihook.pytest_assertion_pass.get_hookimpls(): - - def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: - ihook.pytest_assertion_pass(item=item, lineno=lineno, orig=orig, expl=expl) - - util._assertion_pass = call_assertion_pass_hook - - try: - return (yield) - finally: - util._reprcompare, util._assertion_pass = saved_assert_hooks - util._config = None - - -def pytest_sessionfinish(session: Session) -> None: - assertstate = session.config.stash.get(assertstate_key, None) - if assertstate: - if assertstate.hook is not None: - assertstate.hook.set_session(None) - - -def pytest_assertrepr_compare( - config: Config, op: str, left: Any, right: Any -) -> list[str] | None: - if config.pluginmanager.has_plugin("terminalreporter"): - highlighter = config.get_terminal_writer()._highlight - else: - # Keep it plaintext when not using terminalrepoterer (#14377). - highlighter = util.dummy_highlighter - explanation = list( - util.assertrepr_compare( - op=op, - left=left, - right=right, - verbose=config.get_verbosity(Config.VERBOSITY_ASSERTIONS), - highlighter=highlighter, - assertion_text_diff_style=util.get_assertion_text_diff_style(config), - ) - ) - return explanation or None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 778edd1..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_any.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_any.cpython-311.pyc deleted file mode 100644 index 3e0de1a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_any.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_mapping.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_mapping.cpython-311.pyc deleted file mode 100644 index eadeba9..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_mapping.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_sequence.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_sequence.cpython-311.pyc deleted file mode 100644 index 026586f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_sequence.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_set.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_set.cpython-311.pyc deleted file mode 100644 index 4087a22..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_compare_set.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_guards.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_guards.cpython-311.pyc deleted file mode 100644 index 83f2315..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_guards.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_typing.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_typing.cpython-311.pyc deleted file mode 100644 index 749bfa0..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/_typing.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/compare_text.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/compare_text.cpython-311.pyc deleted file mode 100644 index 7e4a52a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/compare_text.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/highlight.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/highlight.cpython-311.pyc deleted file mode 100644 index d451f3d..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/highlight.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-311.pyc deleted file mode 100644 index 6ee1734..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/truncate.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/truncate.cpython-311.pyc deleted file mode 100644 index d7f4610..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/truncate.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/util.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/util.cpython-311.pyc deleted file mode 100644 index cb032c1..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/__pycache__/util.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_any.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_any.py deleted file mode 100644 index 9e57768..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_any.py +++ /dev/null @@ -1,131 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterator -import dataclasses -import pprint - -from _pytest.assertion._compare_mapping import _compare_eq_mapping -from _pytest.assertion._compare_sequence import _compare_eq_iterable -from _pytest.assertion._compare_sequence import _compare_eq_sequence -from _pytest.assertion._compare_set import _compare_eq_set -from _pytest.assertion._guards import has_default_eq -from _pytest.assertion._guards import isattrs -from _pytest.assertion._guards import isdatacls -from _pytest.assertion._guards import isiterable -from _pytest.assertion._guards import ismapping -from _pytest.assertion._guards import isnamedtuple -from _pytest.assertion._guards import issequence -from _pytest.assertion._guards import isset -from _pytest.assertion._guards import istext -from _pytest.assertion._typing import _AssertionTextDiffStyle -from _pytest.assertion._typing import _HighlightFunc -from _pytest.assertion.compare_text import _compare_eq_text - - -def _compare_eq_any( - left: object, - right: object, - highlighter: _HighlightFunc, - verbose: int, - assertion_text_diff_style: _AssertionTextDiffStyle, -) -> Iterator[str]: - """Yield the per-line explanation for ``left == right`` (without summary). - - Yields nothing when no specialised explanation applies, so consumers - can stream the output and bail out early (e.g. for truncation) without - materialising the entire diff first. - """ - if istext(left) and istext(right): - yield from _compare_eq_text( - left, - right, - highlighter, - verbose, - assertion_text_diff_style, - ) - else: - from _pytest.python_api import ApproxBase - - # Although the common order should be obtained == approx(...), allow both ways. - if isinstance(right, ApproxBase): - yield from right._repr_compare(left) - elif isinstance(left, ApproxBase): - yield from left._repr_compare(right) - elif type(left) is type(right) and ( - isdatacls(left) or isattrs(left) or isnamedtuple(left) - ): - # Note: unlike dataclasses/attrs, namedtuples compare only the - # field values, not the type or field names. But this branch - # intentionally only handles the same-type case, which was often - # used in older code bases before dataclasses/attrs were available. - yield from _compare_eq_cls( - left, - right, - highlighter, - verbose, - assertion_text_diff_style, - ) - elif issequence(left) and issequence(right): - yield from _compare_eq_sequence(left, right, highlighter, verbose) - elif isset(left) and isset(right): - yield from _compare_eq_set(left, right, highlighter, verbose) - elif ismapping(left) and ismapping(right): - yield from _compare_eq_mapping(left, right, highlighter, verbose) - - if isiterable(left) and isiterable(right): - yield from _compare_eq_iterable(left, right, highlighter, verbose) - - -def _compare_eq_cls( - left: object, - right: object, - highlighter: _HighlightFunc, - verbose: int, - assertion_text_diff_style: _AssertionTextDiffStyle, -) -> Iterator[str]: - if not has_default_eq(left): - return - if isdatacls(left): - all_fields = dataclasses.fields(left) - fields_to_check = [info.name for info in all_fields if info.compare] - elif isattrs(left): - all_fields = left.__attrs_attrs__ # type: ignore[attr-defined] - fields_to_check = [field.name for field in all_fields if getattr(field, "eq")] - elif isnamedtuple(left): - fields_to_check = left._fields # type: ignore[attr-defined] - else: - assert False - - indent = " " - same = [] - diff = [] - for field in fields_to_check: - if getattr(left, field) == getattr(right, field): - same.append(field) - else: - diff.append(field) - - if same or diff: - yield "" - if same and verbose < 2: - yield f"Omitting {len(same)} identical items, use -vv to show" - elif same: - yield "Matching attributes:" - yield from highlighter(pprint.pformat(same)).splitlines() - if diff: - yield "Differing attributes:" - yield from highlighter(pprint.pformat(diff)).splitlines() - for field in diff: - field_left = getattr(left, field) - field_right = getattr(right, field) - yield "" - yield f"Drill down into differing attribute {field}:" - yield f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}" - for line in _compare_eq_any( - field_left, - field_right, - highlighter, - verbose, - assertion_text_diff_style, - ): - yield indent + line diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_mapping.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_mapping.py deleted file mode 100644 index 4edb470..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_mapping.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterator -from collections.abc import Mapping -import pprint - -from _pytest._io.saferepr import saferepr -from _pytest.assertion._typing import _HighlightFunc - - -def _compare_eq_mapping( - left: Mapping[object, object], - right: Mapping[object, object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - set_left = set(left) - set_right = set(right) - common = set_left.intersection(set_right) - same = {k: left[k] for k in common if left[k] == right[k]} - if same and verbose < 2: - yield f"Omitting {len(same)} identical items, use -vv to show" - elif same: - yield "Common items:" - yield from highlighter(pprint.pformat(same)).splitlines() - diff = {k for k in common if left[k] != right[k]} - if diff: - yield "Differing items:" - for k in diff: - yield ( - highlighter(saferepr({k: left[k]})) - + " != " - + highlighter(saferepr({k: right[k]})) - ) - extra_left = set_left - set_right - len_extra_left = len(extra_left) - if len_extra_left: - yield f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:" - yield from highlighter( - pprint.pformat({k: left[k] for k in extra_left}) - ).splitlines() - extra_right = set_right - set_left - len_extra_right = len(extra_right) - if len_extra_right: - yield f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:" - yield from highlighter( - pprint.pformat({k: right[k] for k in extra_right}) - ).splitlines() diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_sequence.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_sequence.py deleted file mode 100644 index cd0043b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_sequence.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Sequence - -from _pytest._io.pprint import PrettyPrinter -from _pytest._io.saferepr import saferepr -from _pytest.assertion._typing import _HighlightFunc -from _pytest.compat import running_on_ci - - -def _compare_eq_iterable( - left: Iterable[object], - right: Iterable[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - if verbose <= 0 and not running_on_ci(): - yield "Use -v to get more diff" - return - # dynamic import to speedup pytest - import difflib - - left_formatting = PrettyPrinter().pformat(left).splitlines() - right_formatting = PrettyPrinter().pformat(right).splitlines() - - yield "" - yield "Full diff:" - # "right" is the expected base against which we compare "left", - # see https://github.com/pytest-dev/pytest/issues/3333 - yield from highlighter( - "\n".join( - line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting) - ), - lexer="diff", - ).splitlines() - - -def _compare_eq_sequence( - left: Sequence[object], - right: Sequence[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) - len_left = len(left) - len_right = len(right) - for i in range(min(len_left, len_right)): - if left[i] != right[i]: - if comparing_bytes: - # when comparing bytes, we want to see their ascii representation - # instead of their numeric values (#5260) - # using a slice gives us the ascii representation: - # >>> s = b'foo' - # >>> s[0] - # 102 - # >>> s[0:1] - # b'f' - left_value: object = left[i : i + 1] - right_value: object = right[i : i + 1] - else: - left_value = left[i] - right_value = right[i] - - yield ( - f"At index {i} diff:" - f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" - ) - break - - if comparing_bytes: - # when comparing bytes, it doesn't help to show the "sides contain one or more - # items" longer explanation, so skip it - return - - len_diff = len_left - len_right - if len_diff: - if len_diff > 0: - dir_with_more = "Left" - extra = saferepr(left[len_right]) - else: - len_diff = 0 - len_diff - dir_with_more = "Right" - extra = saferepr(right[len_left]) - - if len_diff == 1: - yield f"{dir_with_more} contains one more item: {highlighter(extra)}" - else: - yield f"{dir_with_more} contains {len_diff} more items, first extra item: {highlighter(extra)}" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_set.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_set.py deleted file mode 100644 index 2817133..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_compare_set.py +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Iterator -from collections.abc import Set as AbstractSet -from typing import TypeAlias - -from _pytest._io.saferepr import saferepr -from _pytest.assertion._typing import _HighlightFunc - - -def _set_one_sided_diff( - posn: str, - set1: AbstractSet[object], - set2: AbstractSet[object], - highlighter: _HighlightFunc, -) -> Iterator[str]: - diff = set1 - set2 - if diff: - yield f"Extra items in the {posn} set:" - for item in diff: - yield highlighter(saferepr(item)) - - -def _compare_eq_set( - left: AbstractSet[object], - right: AbstractSet[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - yield from _set_one_sided_diff("left", left, right, highlighter) - yield from _set_one_sided_diff("right", right, left, highlighter) - - -def _compare_gte_set( - left: AbstractSet[object], - right: AbstractSet[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - yield from _set_one_sided_diff("right", right, left, highlighter) - - -def _compare_lte_set( - left: AbstractSet[object], - right: AbstractSet[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - yield from _set_one_sided_diff("left", left, right, highlighter) - - -def _compare_gt_set( - left: AbstractSet[object], - right: AbstractSet[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - if left == right: - yield "Both sets are equal" - else: - yield from _set_one_sided_diff("right", right, left, highlighter) - - -def _compare_lt_set( - left: AbstractSet[object], - right: AbstractSet[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - if left == right: - yield "Both sets are equal" - else: - yield from _set_one_sided_diff("left", left, right, highlighter) - - -SetComparisonFunction: TypeAlias = Callable[ - [AbstractSet[object], AbstractSet[object], _HighlightFunc, int], - Iterator[str], -] - - -def _both_sets_are_equal( - left: AbstractSet[object], - right: AbstractSet[object], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> Iterator[str]: - yield "Both sets are equal" - - -SET_COMPARISON_FUNCTIONS: dict[str, SetComparisonFunction] = { - # == can't be done here without a prior refactor because there's an additional - # explanation for iterable in _compare_eq_any - # "==": _compare_eq_set, - "!=": _both_sets_are_equal, - ">=": _compare_gte_set, - "<=": _compare_lte_set, - ">": _compare_gt_set, - "<": _compare_lt_set, -} diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_guards.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_guards.py deleted file mode 100644 index bb9fedd..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_guards.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -import collections.abc -from collections.abc import Mapping -import dataclasses -from typing import TypeGuard - - -def issequence(x: object) -> TypeGuard[collections.abc.Sequence[object]]: - return isinstance(x, collections.abc.Sequence) and not isinstance(x, str) - - -def istext(x: object) -> TypeGuard[str]: - return isinstance(x, str) - - -def ismapping(x: object) -> TypeGuard[Mapping[object, object]]: - return isinstance(x, Mapping) - - -def isset(x: object) -> TypeGuard[set[object] | frozenset[object]]: - return isinstance(x, set | frozenset) - - -def isnamedtuple(obj: object) -> bool: - return isinstance(obj, tuple) and getattr(obj, "_fields", None) is not None - - -isdatacls = dataclasses.is_dataclass - - -def isattrs(obj: object) -> bool: - return getattr(obj, "__attrs_attrs__", None) is not None - - -def isiterable(obj: object) -> TypeGuard[collections.abc.Iterable[object]]: - try: - iter(obj) # type: ignore[call-overload] - return not istext(obj) - except Exception: - return False - - -def has_default_eq(obj: object) -> bool: - """Check if an instance of an object contains the default eq - - First, we check if the object's __eq__ attribute has __code__, - if so, we check the equally of the method code filename (__code__.co_filename) - to the default one generated by the dataclass and attr module - for dataclasses the default co_filename is , for attrs class, the __eq__ should contain "attrs eq generated" - """ - # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68 - if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"): - code_filename = obj.__eq__.__code__.co_filename - - if isattrs(obj): - return "attrs generated " in code_filename - - return code_filename == "" # data class - return True diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_typing.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_typing.py deleted file mode 100644 index f032f34..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/_typing.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -from typing import Literal -from typing import Protocol - - -_AssertionTextDiffStyle = Literal["ndiff", "block"] - - -class _HighlightFunc(Protocol): # noqa: PYI046 - def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str: - """Apply highlighting to the given source.""" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/compare_text.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/compare_text.py deleted file mode 100644 index 3109644..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/compare_text.py +++ /dev/null @@ -1,104 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterator - -from _pytest._io.saferepr import saferepr -from _pytest.assertion._typing import _AssertionTextDiffStyle -from _pytest.assertion._typing import _HighlightFunc -from _pytest.assertion.highlight import dummy_highlighter -from _pytest.compat import assert_never - - -def _compare_eq_text( - left: str, - right: str, - highlighter: _HighlightFunc, - verbose: int, - assertion_text_diff_style: _AssertionTextDiffStyle, -) -> Iterator[str]: - match assertion_text_diff_style: - case "block": - yield from _diff_text_block(left, right) - case "ndiff": - yield from _diff_text(left, right, highlighter, verbose) - case unreachable: - assert_never(unreachable) - - -def _diff_text_block(left: str, right: str) -> Iterator[str]: - yield "Left:" - yield from _format_text_block_lines(left) - yield "" - yield "Right:" - yield from _format_text_block_lines(right) - - -def _format_text_block_lines(text: str) -> Iterator[str]: - for line in text.split("\n"): - yield f" {line}" - - -def _diff_text( - left: str, right: str, highlighter: _HighlightFunc, verbose: int = 0 -) -> Iterator[str]: - """Yield the explanation for the diff between text. - - Unless --verbose is used this will skip leading and trailing - characters which are identical to keep the diff minimal. - """ - from difflib import ndiff - - if verbose < 1: - i = 0 # just in case left or right has zero length - for i in range(min(len(left), len(right))): - if left[i] != right[i]: - break - if i > 42: - i -= 10 # Provide some context - yield f"Skipping {i} identical leading characters in diff, use -v to show" - left = left[i:] - right = right[i:] - if len(left) == len(right): - for i in range(len(left)): - if left[-i] != right[-i]: - break - if i > 42: - i -= 10 # Provide some context - yield ( - f"Skipping {i} identical trailing " - "characters in diff, use -v to show" - ) - left = left[:-i] - right = right[:-i] - keepends = True - if left.isspace() or right.isspace(): - left = repr(str(left)) - right = repr(str(right)) - yield "Strings contain only whitespace, escaping them using repr()" - # "right" is the expected base against which we compare "left", - # see https://github.com/pytest-dev/pytest/issues/3333 - yield from highlighter( - "\n".join( - line.strip("\n") - for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) - ), - lexer="diff", - ).splitlines() - - -def _notin_text(term: str, text: str, verbose: int = 0) -> Iterator[str]: - index = text.find(term) - head = text[:index] - tail = text[index + len(term) :] - correct_text = head + tail - diff = _diff_text(text, correct_text, dummy_highlighter, verbose) - yield f"{saferepr(term, maxsize=42)} is contained here:" - for line in diff: - if line.startswith("Skipping"): - continue - if line.startswith("- "): - continue - if line.startswith("+ "): - yield " " + line[2:] - else: - yield line diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/highlight.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/highlight.py deleted file mode 100644 index 9ae833f..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/highlight.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from typing import Literal - - -def dummy_highlighter(source: str, lexer: Literal["diff", "python"] = "python") -> str: - """Dummy highlighter that returns the text unprocessed. - - Needed for _notin_text, as the diff gets post-processed to only show the "+" part. - """ - return source diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/rewrite.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/rewrite.py deleted file mode 100644 index 99815b7..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/rewrite.py +++ /dev/null @@ -1,1193 +0,0 @@ -"""Rewrite assertion AST to produce nice error messages.""" - -from __future__ import annotations - -import ast -from collections import defaultdict -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Sequence -import errno -import functools -import importlib.abc -import importlib.machinery -import importlib.util -import io -import itertools -import marshal -import os -from pathlib import Path -from pathlib import PurePath -import struct -import sys -import tokenize -import types -from typing import IO -from typing import TYPE_CHECKING - - -if sys.version_info >= (3, 12): - from importlib.resources.abc import TraversableResources -else: - from importlib.abc import TraversableResources -if sys.version_info < (3, 11): - from importlib.readers import FileReader -else: - from importlib.resources.readers import FileReader - - -from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE -from _pytest._io.saferepr import saferepr -from _pytest._io.saferepr import saferepr_unlimited -from _pytest._version import version -from _pytest.assertion import util -from _pytest.config import Config -from _pytest.fixtures import FixtureFunctionDefinition -from _pytest.main import Session -from _pytest.pathlib import absolutepath -from _pytest.pathlib import fnmatch_ex -from _pytest.stash import StashKey - - -# fmt: off -from _pytest.assertion.util import format_explanation as _format_explanation # noqa:F401, isort:skip -# fmt:on - -if TYPE_CHECKING: - from _pytest.assertion import AssertionState - - -class Sentinel: - pass - - -assertstate_key = StashKey["AssertionState"]() - -# pytest caches rewritten pycs in pycache dirs -PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}" -PYC_EXT = ".py" + ((__debug__ and "c") or "o") -PYC_TAIL = "." + PYTEST_TAG + PYC_EXT - -# Special marker that denotes we have just left a scope definition -_SCOPE_END_MARKER = Sentinel() - - -class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): - """PEP302/PEP451 import hook which rewrites asserts.""" - - def __init__(self, config: Config) -> None: - self.config = config - try: - self.fnpats = config.getini("python_files") - except ValueError: - self.fnpats = ["test_*.py", "*_test.py"] - self.session: Session | None = None - self._rewritten_names: dict[str, Path] = {} - self._must_rewrite: set[str] = set() - # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, - # which might result in infinite recursion (#3506) - self._writing_pyc = False - self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache: dict[str, bool] = {} - self._session_paths_checked = False - - def set_session(self, session: Session | None) -> None: - self.session = session - self._session_paths_checked = False - - # Indirection so we can mock calls to find_spec originated from the hook during testing - _find_spec = importlib.machinery.PathFinder.find_spec - - def find_spec( - self, - name: str, - path: Sequence[str | bytes] | None = None, - target: types.ModuleType | None = None, - ) -> importlib.machinery.ModuleSpec | None: - if self._writing_pyc: - return None - state = self.config.stash[assertstate_key] - if self._early_rewrite_bailout(name, state): - return None - state.trace(f"find_module called for: {name}") - - # Type ignored because mypy is confused about the `self` binding here. - spec = self._find_spec(name, path) # type: ignore - - if ( - # the import machinery could not find a file to import - spec is None - # this is a namespace package (without `__init__.py`) - # there's nothing to rewrite there - or spec.origin is None - # we can only rewrite source files - or not isinstance(spec.loader, importlib.machinery.SourceFileLoader) - # if the file doesn't exist, we can't rewrite it - or not os.path.exists(spec.origin) - ): - return None - else: - fn = spec.origin - - if not self._should_rewrite(name, fn, state): - return None - - return importlib.util.spec_from_file_location( - name, - fn, - loader=self, - submodule_search_locations=spec.submodule_search_locations, - ) - - def create_module( - self, spec: importlib.machinery.ModuleSpec - ) -> types.ModuleType | None: - return None # default behaviour is fine - - def exec_module(self, module: types.ModuleType) -> None: - assert module.__spec__ is not None - assert module.__spec__.origin is not None - fn = Path(module.__spec__.origin) - state = self.config.stash[assertstate_key] - - self._rewritten_names[module.__name__] = fn - - # The requested module looks like a test file, so rewrite it. This is - # the most magical part of the process: load the source, rewrite the - # asserts, and load the rewritten source. We also cache the rewritten - # module code in a special pyc. We must be aware of the possibility of - # concurrent pytest processes rewriting and loading pycs. To avoid - # tricky race conditions, we maintain the following invariant: The - # cached pyc is always a complete, valid pyc. Operations on it must be - # atomic. POSIX's atomic rename comes in handy. - write = not sys.dont_write_bytecode - cache_dir = get_cache_dir(fn) - if write: - ok = try_makedirs(cache_dir) - if not ok: - write = False - state.trace(f"read only directory: {cache_dir}") - - cache_name = fn.name[:-3] + PYC_TAIL - pyc = cache_dir / cache_name - # Notice that even if we're in a read-only directory, I'm going - # to check for a cached pyc. This may not be optimal... - co = _read_pyc(fn, pyc, state.trace) - if co is None: - state.trace(f"rewriting {fn!r}") - source_stat, co = _rewrite_test(fn, self.config) - if write: - self._writing_pyc = True - try: - _write_pyc(state, co, source_stat, pyc) - finally: - self._writing_pyc = False - else: - state.trace(f"found cached rewritten pyc for {fn}") - exec(co, module.__dict__) - - def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: - """A fast way to get out of rewriting modules. - - Profiling has shown that the call to PathFinder.find_spec (inside of - the find_spec from this class) is a major slowdown, so, this method - tries to filter what we're sure won't be rewritten before getting to - it. - """ - if self.session is not None and not self._session_paths_checked: - self._session_paths_checked = True - for initial_path in self.session._initialpaths: - # Make something as c:/projects/my_project/path.py -> - # ['c:', 'projects', 'my_project', 'path.py'] - parts = str(initial_path).split(os.sep) - # add 'path' to basenames to be checked. - self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) - - # Note: conftest already by default in _basenames_to_check_rewrite. - parts = name.split(".") - if parts[-1] in self._basenames_to_check_rewrite: - return False - - # For matching the name it must be as if it was a filename. - path = PurePath(*parts).with_suffix(".py") - - for pat in self.fnpats: - # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based - # on the name alone because we need to match against the full path - if os.path.dirname(pat): - return False - if fnmatch_ex(pat, path): - return False - - if self._is_marked_for_rewrite(name, state): - return False - - state.trace(f"early skip of rewriting module: {name}") - return True - - def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: - # always rewrite conftest files - if os.path.basename(fn) == "conftest.py": - state.trace(f"rewriting conftest file: {fn!r}") - return True - - if self.session is not None: - if self.session.isinitpath(absolutepath(fn)): - state.trace(f"matched test file (was specified on cmdline): {fn!r}") - return True - - # modules not passed explicitly on the command line are only - # rewritten if they match the naming convention for test files - fn_path = PurePath(fn) - for pat in self.fnpats: - if fnmatch_ex(pat, fn_path): - state.trace(f"matched test file {fn!r}") - return True - - return self._is_marked_for_rewrite(name, state) - - def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: - try: - return self._marked_for_rewrite_cache[name] - except KeyError: - for marked in self._must_rewrite: - if name == marked or name.startswith(marked + "."): - state.trace(f"matched marked file {name!r} (from {marked!r})") - self._marked_for_rewrite_cache[name] = True - return True - - self._marked_for_rewrite_cache[name] = False - return False - - def mark_rewrite(self, *names: str) -> None: - """Mark import names as needing to be rewritten. - - The named module or package as well as any nested modules will - be rewritten on import. - """ - already_imported = ( - set(names).intersection(sys.modules).difference(self._rewritten_names) - ) - for name in already_imported: - mod = sys.modules[name] - if not AssertionRewriter.is_rewrite_disabled( - mod.__doc__ or "" - ) and not isinstance(mod.__loader__, type(self)): - self._warn_already_imported(name) - self._must_rewrite.update(names) - self._marked_for_rewrite_cache.clear() - - def _warn_already_imported(self, name: str) -> None: - from _pytest.warning_types import PytestAssertRewriteWarning - - self.config.issue_config_time_warning( - PytestAssertRewriteWarning( - f"Module already imported so cannot be rewritten; {name}" - ), - stacklevel=5, - ) - - def get_data(self, pathname: str | bytes) -> bytes: - """Optional PEP302 get_data API.""" - with open(pathname, "rb") as f: - return f.read() - - def get_resource_reader(self, name: str) -> TraversableResources: - return FileReader(types.SimpleNamespace(path=self._rewritten_names[name])) # type: ignore[arg-type] - - -def _write_pyc_fp( - fp: IO[bytes], source_stat: os.stat_result, co: types.CodeType -) -> None: - # Technically, we don't have to have the same pyc format as - # (C)Python, since these "pycs" should never be seen by builtin - # import. However, there's little reason to deviate. - fp.write(importlib.util.MAGIC_NUMBER) - # https://www.python.org/dev/peps/pep-0552/ - flags = b"\x00\x00\x00\x00" - fp.write(flags) - # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) - mtime = int(source_stat.st_mtime) & 0xFFFFFFFF - size = source_stat.st_size & 0xFFFFFFFF - # " bool: - proc_pyc = f"{pyc}.{os.getpid()}" - try: - with open(proc_pyc, "wb") as fp: - _write_pyc_fp(fp, source_stat, co) - except OSError as e: - state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") - return False - - try: - os.replace(proc_pyc, pyc) - except OSError as e: - state.trace(f"error writing pyc file at {pyc}: {e}") - # we ignore any failure to write the cache file - # there are many reasons, permission-denied, pycache dir being a - # file etc. - return False - return True - - -def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: - """Read and rewrite *fn* and return the code object.""" - stat = os.stat(fn) - source = fn.read_bytes() - strfn = str(fn) - tree = ast.parse(source, filename=strfn) - rewrite_asserts(tree, source, strfn, config) - co = compile(tree, strfn, "exec", dont_inherit=True) - return stat, co - - -def _read_pyc( - source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None -) -> types.CodeType | None: - """Possibly read a pytest pyc containing rewritten code. - - Return rewritten code if successful or None if not. - """ - try: - fp = open(pyc, "rb") - except OSError: - return None - with fp: - try: - stat_result = os.stat(source) - mtime = int(stat_result.st_mtime) - size = stat_result.st_size - data = fp.read(16) - except OSError as e: - trace(f"_read_pyc({source}): OSError {e}") - return None - # Check for invalid or out of date pyc file. - if len(data) != (16): - trace(f"_read_pyc({source}): invalid pyc (too short)") - return None - if data[:4] != importlib.util.MAGIC_NUMBER: - trace(f"_read_pyc({source}): invalid pyc (bad magic number)") - return None - if data[4:8] != b"\x00\x00\x00\x00": - trace(f"_read_pyc({source}): invalid pyc (unsupported flags)") - return None - mtime_data = data[8:12] - if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: - trace(f"_read_pyc({source}): out of date") - return None - size_data = data[12:16] - if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: - trace(f"_read_pyc({source}): invalid pyc (incorrect size)") - return None - try: - co = marshal.load(fp) - except Exception as e: - trace(f"_read_pyc({source}): marshal.load error {e}") - return None - if not isinstance(co, types.CodeType): - trace(f"_read_pyc({source}): not a code object") - return None - return co - - -def rewrite_asserts( - mod: ast.Module, - source: bytes, - module_path: str | None = None, - config: Config | None = None, -) -> None: - """Rewrite the assert statements in mod.""" - AssertionRewriter(module_path, config, source).run(mod) - - -def _saferepr(obj: object) -> str: - r"""Get a safe repr of an object for assertion error messages. - - The assertion formatting (util.format_explanation()) requires - newlines to be escaped since they are a special character for it. - Normally assertion.util.format_explanation() does this but for a - custom repr it is possible to contain one of the special escape - sequences, especially '\n{' and '\n}' are likely to be present in - JSON reprs. - """ - if isinstance(obj, types.MethodType): - # for bound methods, skip redundant information - return obj.__name__ - - maxsize = _get_maxsize_for_saferepr(util._config) - if not maxsize: - return saferepr_unlimited(obj).replace("\n", "\\n") - return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") - - -def _get_maxsize_for_saferepr(config: Config | None) -> int | None: - """Get `maxsize` configuration for saferepr based on the given config object.""" - if config is None: - verbosity = 0 - else: - verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - if verbosity >= 2: - return None - if verbosity >= 1: - return DEFAULT_REPR_MAX_SIZE * 10 - return DEFAULT_REPR_MAX_SIZE - - -def _format_assertmsg(obj: object) -> str: - r"""Format the custom assertion message given. - - For strings this simply replaces newlines with '\n~' so that - util.format_explanation() will preserve them instead of escaping - newlines. For other objects saferepr() is used first. - """ - # reprlib appears to have a bug which means that if a string - # contains a newline it gets escaped, however if an object has a - # .__repr__() which contains newlines it does not get escaped. - # However in either case we want to preserve the newline. - replaces = [("\n", "\n~"), ("%", "%%")] - if not isinstance(obj, str): - obj = saferepr(obj, _get_maxsize_for_saferepr(util._config)) - replaces.append(("\\n", "\n~")) - - for r1, r2 in replaces: - obj = obj.replace(r1, r2) - - return obj - - -def _should_repr_global_name(obj: object) -> bool: - if callable(obj): - # For pytest fixtures the __repr__ method provides more information than the function name. - return isinstance(obj, FixtureFunctionDefinition) - - try: - return not hasattr(obj, "__name__") - except Exception: - return True - - -def _format_boolop(explanations: Iterable[str], is_or: bool) -> str: - explanation = "(" + ((is_or and " or ") or " and ").join(explanations) + ")" - return explanation.replace("%", "%%") - - -def _call_reprcompare( - ops: Sequence[str], - results: Sequence[bool], - expls: Sequence[str], - each_obj: Sequence[object], -) -> str: - for i, res, expl in zip(range(len(ops)), results, expls, strict=True): - try: - done = not res - except Exception: - done = True - if done: - break - if util._reprcompare is not None: - custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1]) - if custom is not None: - return custom - return expl - - -def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None: - if util._assertion_pass is not None: - util._assertion_pass(lineno, orig, expl) - - -def _check_if_assertion_pass_impl() -> bool: - """Check if any plugins implement the pytest_assertion_pass hook - in order not to generate explanation unnecessarily (might be expensive).""" - return True if util._assertion_pass else False - - -UNARY_MAP = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"} - -BINOP_MAP = { - ast.BitOr: "|", - ast.BitXor: "^", - ast.BitAnd: "&", - ast.LShift: "<<", - ast.RShift: ">>", - ast.Add: "+", - ast.Sub: "-", - ast.Mult: "*", - ast.Div: "/", - ast.FloorDiv: "//", - ast.Mod: "%%", # escaped for string formatting - ast.Eq: "==", - ast.NotEq: "!=", - ast.Lt: "<", - ast.LtE: "<=", - ast.Gt: ">", - ast.GtE: ">=", - ast.Pow: "**", - ast.Is: "is", - ast.IsNot: "is not", - ast.In: "in", - ast.NotIn: "not in", - ast.MatMult: "@", -} - - -def traverse_node(node: ast.AST) -> Iterator[ast.AST]: - """Recursively yield node and all its children in depth-first order.""" - yield node - for child in ast.iter_child_nodes(node): - yield from traverse_node(child) - - -@functools.lru_cache(maxsize=1) -def _get_assertion_exprs(src: bytes) -> dict[int, str]: - """Return a mapping from {lineno: "assertion test expression"}.""" - ret: dict[int, str] = {} - - depth = 0 - lines: list[str] = [] - assert_lineno: int | None = None - seen_lines: set[int] = set() - - def _write_and_reset() -> None: - nonlocal depth, lines, assert_lineno, seen_lines - assert assert_lineno is not None - ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\") - depth = 0 - lines = [] - assert_lineno = None - seen_lines = set() - - tokens = tokenize.tokenize(io.BytesIO(src).readline) - for tp, source, (lineno, offset), _, line in tokens: - if tp == tokenize.NAME and source == "assert": - assert_lineno = lineno - elif assert_lineno is not None: - # keep track of depth for the assert-message `,` lookup - if tp == tokenize.OP and source in "([{": - depth += 1 - elif tp == tokenize.OP and source in ")]}": - depth -= 1 - - if not lines: - lines.append(line[offset:]) - seen_lines.add(lineno) - # a non-nested comma separates the expression from the message - elif depth == 0 and tp == tokenize.OP and source == ",": - # one line assert with message - if lineno in seen_lines and len(lines) == 1: - offset_in_trimmed = offset + len(lines[-1]) - len(line) - lines[-1] = lines[-1][:offset_in_trimmed] - # multi-line assert with message - elif lineno in seen_lines: - lines[-1] = lines[-1][:offset] - # multi line assert with escaped newline before message - else: - lines.append(line[:offset]) - _write_and_reset() - elif tp in {tokenize.NEWLINE, tokenize.ENDMARKER}: - _write_and_reset() - elif lines and lineno not in seen_lines: - lines.append(line) - seen_lines.add(lineno) - - return ret - - -class AssertionRewriter(ast.NodeVisitor): - """Assertion rewriting implementation. - - The main entrypoint is to call .run() with an ast.Module instance, - this will then find all the assert statements and rewrite them to - provide intermediate values and a detailed assertion error. See - http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html - for an overview of how this works. - - The entry point here is .run() which will iterate over all the - statements in an ast.Module and for each ast.Assert statement it - finds call .visit() with it. Then .visit_Assert() takes over and - is responsible for creating new ast statements to replace the - original assert statement: it rewrites the test of an assertion - to provide intermediate values and replace it with an if statement - which raises an assertion error with a detailed explanation in - case the expression is false and calls pytest_assertion_pass hook - if expression is true. - - For this .visit_Assert() uses the visitor pattern to visit all the - AST nodes of the ast.Assert.test field, each visit call returning - an AST node and the corresponding explanation string. During this - state is kept in several instance attributes: - - :statements: All the AST statements which will replace the assert - statement. - - :variables: This is populated by .variable() with each variable - used by the statements so that they can all be set to None at - the end of the statements. - - :variable_counter: Counter to create new unique variables needed - by statements. Variables are created using .variable() and - have the form of "@py_assert0". - - :expl_stmts: The AST statements which will be executed to get - data from the assertion. This is the code which will construct - the detailed assertion message that is used in the AssertionError - or for the pytest_assertion_pass hook. - - :explanation_specifiers: A dict filled by .explanation_param() - with %-formatting placeholders and their corresponding - expressions to use in the building of an assertion message. - This is used by .pop_format_context() to build a message. - - :stack: A stack of the explanation_specifiers dicts maintained by - .push_format_context() and .pop_format_context() which allows - to build another %-formatted string while already building one. - - :scope: A tuple containing the current scope used for variables_overwrite. - - :variables_overwrite: A dict filled with references to variables - that change value within an assert. This happens when a variable is - reassigned with the walrus operator - - This state, except the variables_overwrite, is reset on every new assert - statement visited and used by the other visitors. - """ - - def __init__( - self, module_path: str | None, config: Config | None, source: bytes - ) -> None: - super().__init__() - self.module_path = module_path - self.config = config - if config is not None: - self.enable_assertion_pass_hook = config.getini( - "enable_assertion_pass_hook" - ) - else: - self.enable_assertion_pass_hook = False - self.source = source - self.scope: tuple[ast.AST, ...] = () - self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = ( - defaultdict(dict) - ) - - def run(self, mod: ast.Module) -> None: - """Find all assert statements in *mod* and rewrite them.""" - if not mod.body: - # Nothing to do. - return - - # We'll insert some special imports at the top of the module, but after any - # docstrings and __future__ imports, so first figure out where that is. - doc = getattr(mod, "docstring", None) - expect_docstring = doc is None - if doc is not None and self.is_rewrite_disabled(doc): - return - pos = 0 - for item in mod.body: - match item: - case ast.Expr(value=ast.Constant(value=str() as doc)) if ( - expect_docstring - ): - if self.is_rewrite_disabled(doc): - return - expect_docstring = False - case ast.ImportFrom(level=0, module="__future__"): - pass - case _: - break - pos += 1 - # Special case: for a decorated function, set the lineno to that of the - # first decorator, not the `def`. Issue #4984. - if isinstance(item, ast.FunctionDef) and item.decorator_list: - lineno = item.decorator_list[0].lineno - else: - lineno = item.lineno - # Now actually insert the special imports. - aliases = [ - ast.alias("builtins", "@py_builtins", lineno=lineno, col_offset=0), - ast.alias( - "_pytest.assertion.rewrite", - "@pytest_ar", - lineno=lineno, - col_offset=0, - ), - ] - imports = [ - ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases - ] - mod.body[pos:pos] = imports - - # Collect asserts. - self.scope = (mod,) - nodes: list[ast.AST | Sentinel] = [mod] - while nodes: - node = nodes.pop() - if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef): - self.scope = tuple((*self.scope, node)) - nodes.append(_SCOPE_END_MARKER) - if node == _SCOPE_END_MARKER: - self.scope = self.scope[:-1] - continue - assert isinstance(node, ast.AST) - for name, field in ast.iter_fields(node): - if isinstance(field, list): - new: list[ast.AST] = [] - for i, child in enumerate(field): - if isinstance(child, ast.Assert): - # Transform assert. - new.extend(self.visit(child)) - else: - new.append(child) - if isinstance(child, ast.AST): - nodes.append(child) - setattr(node, name, new) - elif ( - isinstance(field, ast.AST) - # Don't recurse into expressions as they can't contain - # asserts. - and not isinstance(field, ast.expr) - ): - nodes.append(field) - - @staticmethod - def is_rewrite_disabled(docstring: str) -> bool: - return "PYTEST_DONT_REWRITE" in docstring - - def variable(self) -> str: - """Get a new variable.""" - # Use a character invalid in python identifiers to avoid clashing. - name = "@py_assert" + str(next(self.variable_counter)) - self.variables.append(name) - return name - - def assign(self, expr: ast.expr) -> ast.Name: - """Give *expr* a name.""" - name = self.variable() - self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) - return ast.copy_location(ast.Name(name, ast.Load()), expr) - - def display(self, expr: ast.expr) -> ast.expr: - """Call saferepr on the expression.""" - return self.helper("_saferepr", expr) - - def helper(self, name: str, *args: ast.expr) -> ast.expr: - """Call a helper in this module.""" - py_name = ast.Name("@pytest_ar", ast.Load()) - attr = ast.Attribute(py_name, name, ast.Load()) - return ast.Call(attr, list(args), []) - - def builtin(self, name: str) -> ast.Attribute: - """Return the builtin called *name*.""" - builtin_name = ast.Name("@py_builtins", ast.Load()) - return ast.Attribute(builtin_name, name, ast.Load()) - - def explanation_param(self, expr: ast.expr) -> str: - """Return a new named %-formatting placeholder for expr. - - This creates a %-formatting placeholder for expr in the - current formatting context, e.g. ``%(py0)s``. The placeholder - and expr are placed in the current format context so that it - can be used on the next call to .pop_format_context(). - """ - specifier = "py" + str(next(self.variable_counter)) - self.explanation_specifiers[specifier] = expr - return "%(" + specifier + ")s" - - def push_format_context(self) -> None: - """Create a new formatting context. - - The format context is used for when an explanation wants to - have a variable value formatted in the assertion message. In - this case the value required can be added using - .explanation_param(). Finally .pop_format_context() is used - to format a string of %-formatted values as added by - .explanation_param(). - """ - self.explanation_specifiers: dict[str, ast.expr] = {} - self.stack.append(self.explanation_specifiers) - - def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: - """Format the %-formatted string with current format context. - - The expl_expr should be an str ast.expr instance constructed from - the %-placeholders created by .explanation_param(). This will - add the required code to format said string to .expl_stmts and - return the ast.Name instance of the formatted string. - """ - current = self.stack.pop() - if self.stack: - self.explanation_specifiers = self.stack[-1] - keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()] - format_dict = ast.Dict(keys, list(current.values())) - form = ast.BinOp(expl_expr, ast.Mod(), format_dict) - name = "@py_format" + str(next(self.variable_counter)) - if self.enable_assertion_pass_hook: - self.format_variables.append(name) - self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) - return ast.Name(name, ast.Load()) - - def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: - """Handle expressions we don't have custom code for.""" - assert isinstance(node, ast.expr) - res = self.assign(node) - return res, self.explanation_param(self.display(res)) - - def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: - """Return the AST statements to replace the ast.Assert instance. - - This rewrites the test of an assertion to provide - intermediate values and replace it with an if statement which - raises an assertion error with a detailed explanation in case - the expression is false. - """ - if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: - import warnings - - from _pytest.warning_types import PytestAssertRewriteWarning - - # TODO: This assert should not be needed. - assert self.module_path is not None - warnings.warn_explicit( - PytestAssertRewriteWarning( - "assertion is always true, perhaps remove parentheses?" - ), - category=None, - filename=self.module_path, - lineno=assert_.lineno, - ) - - self.statements: list[ast.stmt] = [] - self.variables: list[str] = [] - self.variable_counter = itertools.count() - - if self.enable_assertion_pass_hook: - self.format_variables: list[str] = [] - - self.stack: list[dict[str, ast.expr]] = [] - self.expl_stmts: list[ast.stmt] = [] - self.push_format_context() - # Rewrite assert into a bunch of statements. - top_condition, explanation = self.visit(assert_.test) - - negation = ast.UnaryOp(ast.Not(), top_condition) - - if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook - msg = self.pop_format_context(ast.Constant(explanation)) - - # Failed - if assert_.msg: - assertmsg = self.helper("_format_assertmsg", assert_.msg) - gluestr = "\n>assert " - else: - assertmsg = ast.Constant("") - gluestr = "assert " - err_explanation = ast.BinOp(ast.Constant(gluestr), ast.Add(), msg) - err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) - err_name = ast.Name("AssertionError", ast.Load()) - fmt = self.helper("_format_explanation", err_msg) - exc = ast.Call(err_name, [fmt], []) - raise_ = ast.Raise(exc, None) - statements_fail = [] - statements_fail.extend(self.expl_stmts) - statements_fail.append(raise_) - - # Passed - fmt_pass = self.helper("_format_explanation", msg) - orig = _get_assertion_exprs(self.source)[assert_.lineno] - hook_call_pass = ast.Expr( - self.helper( - "_call_assertion_pass", - ast.Constant(assert_.lineno), - ast.Constant(orig), - fmt_pass, - ) - ) - # If any hooks implement assert_pass hook - hook_impl_test = ast.If( - self.helper("_check_if_assertion_pass_impl"), - [*self.expl_stmts, hook_call_pass], - [], - ) - statements_pass: list[ast.stmt] = [hook_impl_test] - - # Test for assertion condition - main_test = ast.If(negation, statements_fail, statements_pass) - self.statements.append(main_test) - if self.format_variables: - variables: list[ast.expr] = [ - ast.Name(name, ast.Store()) for name in self.format_variables - ] - clear_format = ast.Assign(variables, ast.Constant(None)) - self.statements.append(clear_format) - - else: # Original assertion rewriting - # Create failure message. - body = self.expl_stmts - self.statements.append(ast.If(negation, body, [])) - if assert_.msg: - assertmsg = self.helper("_format_assertmsg", assert_.msg) - explanation = "\n>assert " + explanation - else: - assertmsg = ast.Constant("") - explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), ast.Constant(explanation)) - msg = self.pop_format_context(template) - fmt = self.helper("_format_explanation", msg) - err_name = ast.Name("AssertionError", ast.Load()) - exc = ast.Call(err_name, [fmt], []) - raise_ = ast.Raise(exc, None) - - body.append(raise_) - - # Clear temporary variables by setting them to None. - if self.variables: - variables = [ast.Name(name, ast.Store()) for name in self.variables] - clear = ast.Assign(variables, ast.Constant(None)) - self.statements.append(clear) - # Fix locations (line numbers/column offsets). - for stmt in self.statements: - for node in traverse_node(stmt): - if getattr(node, "lineno", None) is None: - # apply the assertion location to all generated ast nodes without source location - # and preserve the location of existing nodes or generated nodes with an correct location. - ast.copy_location(node, assert_) - return self.statements - - def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]: - # This method handles the 'walrus operator' repr of the target - # name if it's a local variable or _should_repr_global_name() - # thinks it's acceptable. - locs = ast.Call(self.builtin("locals"), [], []) - target_id = name.target.id - inlocs = ast.Compare(ast.Constant(target_id), [ast.In()], [locs]) - dorepr = self.helper("_should_repr_global_name", name) - test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) - return name, self.explanation_param(expr) - - def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]: - # Display the repr of the name if it's a local variable or - # _should_repr_global_name() thinks it's acceptable. - locs = ast.Call(self.builtin("locals"), [], []) - inlocs = ast.Compare(ast.Constant(name.id), [ast.In()], [locs]) - dorepr = self.helper("_should_repr_global_name", name) - test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) - return name, self.explanation_param(expr) - - def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: - res_var = self.variable() - expl_list = self.assign(ast.List([], ast.Load())) - app = ast.Attribute(expl_list, "append", ast.Load()) - is_or = int(isinstance(boolop.op, ast.Or)) - body = save = self.statements - fail_save = self.expl_stmts - levels = len(boolop.values) - 1 - self.push_format_context() - # Process each operand, short-circuiting if needed. - for i, v in enumerate(boolop.values): - if i: - fail_inner: list[ast.stmt] = [] - # cond is set in a prior loop iteration below - self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 - self.expl_stmts = fail_inner - match v: - # Check if the left operand is an ast.NamedExpr and the value has already been visited - case ast.Compare( - left=ast.NamedExpr(target=ast.Name(id=target_id)) - ) if target_id in [ - e.id for e in boolop.values[:i] if hasattr(e, "id") - ]: - pytest_temp = self.variable() - self.variables_overwrite[self.scope][target_id] = v.left # type:ignore[assignment] - # mypy's false positive, we're checking that the 'target' attribute exists. - v.left.target.id = pytest_temp # type:ignore[attr-defined] - self.push_format_context() - res, expl = self.visit(v) - body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) - expl_format = self.pop_format_context(ast.Constant(expl)) - call = ast.Call(app, [expl_format], []) - self.expl_stmts.append(ast.Expr(call)) - if i < levels: - cond: ast.expr = res - if is_or: - cond = ast.UnaryOp(ast.Not(), cond) - inner: list[ast.stmt] = [] - self.statements.append(ast.If(cond, inner, [])) - self.statements = body = inner - self.statements = save - self.expl_stmts = fail_save - expl_template = self.helper("_format_boolop", expl_list, ast.Constant(is_or)) - expl = self.pop_format_context(expl_template) - return ast.Name(res_var, ast.Load()), self.explanation_param(expl) - - def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: - pattern = UNARY_MAP[unary.op.__class__] - operand_res, operand_expl = self.visit(unary.operand) - res = self.assign(ast.copy_location(ast.UnaryOp(unary.op, operand_res), unary)) - return res, pattern % (operand_expl,) - - def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: - symbol = BINOP_MAP[binop.op.__class__] - left_expr, left_expl = self.visit(binop.left) - right_expr, right_expl = self.visit(binop.right) - explanation = f"({left_expl} {symbol} {right_expl})" - res = self.assign( - ast.copy_location(ast.BinOp(left_expr, binop.op, right_expr), binop) - ) - return res, explanation - - def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: - new_func, func_expl = self.visit(call.func) - arg_expls = [] - new_args = [] - new_kwargs = [] - for arg in call.args: - if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get( - self.scope, {} - ): - arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment] - res, expl = self.visit(arg) - arg_expls.append(expl) - new_args.append(res) - for keyword in call.keywords: - match keyword.value: - case ast.Name(id=id) if id in self.variables_overwrite.get( - self.scope, {} - ): - keyword.value = self.variables_overwrite[self.scope][id] # type:ignore[assignment] - res, expl = self.visit(keyword.value) - new_kwargs.append(ast.keyword(keyword.arg, res)) - if keyword.arg: - arg_expls.append(keyword.arg + "=" + expl) - else: # **args have `arg` keywords with an .arg of None - arg_expls.append("**" + expl) - - expl = "{}({})".format(func_expl, ", ".join(arg_expls)) - new_call = ast.copy_location(ast.Call(new_func, new_args, new_kwargs), call) - res = self.assign(new_call) - res_expl = self.explanation_param(self.display(res)) - outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" - return res, outer_expl - - def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: - # A Starred node can appear in a function call. - res, expl = self.visit(starred.value) - new_starred = ast.Starred(res, starred.ctx) - return new_starred, "*" + expl - - def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: - if not isinstance(attr.ctx, ast.Load): - return self.generic_visit(attr) - value, value_expl = self.visit(attr.value) - res = self.assign( - ast.copy_location(ast.Attribute(value, attr.attr, ast.Load()), attr) - ) - res_expl = self.explanation_param(self.display(res)) - pat = "%s\n{%s = %s.%s\n}" - expl = pat % (res_expl, res_expl, value_expl, attr.attr) - return res, expl - - def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]: - self.push_format_context() - # We first check if we have overwritten a variable in the previous assert - match comp.left: - case ast.Name(id=name_id) if name_id in self.variables_overwrite.get( - self.scope, {} - ): - comp.left = self.variables_overwrite[self.scope][name_id] # type: ignore[assignment] - case ast.NamedExpr(target=ast.Name(id=target_id)): - self.variables_overwrite[self.scope][target_id] = comp.left # type: ignore[assignment] - left_res, left_expl = self.visit(comp.left) - if isinstance(comp.left, ast.Compare | ast.BoolOp): - left_expl = f"({left_expl})" - res_variables = [self.variable() for i in range(len(comp.ops))] - load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] - store_names = [ast.Name(v, ast.Store()) for v in res_variables] - it = zip(range(len(comp.ops)), comp.ops, comp.comparators, strict=True) - expls: list[ast.expr] = [] - syms: list[ast.expr] = [] - results = [left_res] - for i, op, next_operand in it: - match (next_operand, left_res): - case ( - ast.NamedExpr(target=ast.Name(id=target_id)), - ast.Name(id=name_id), - ) if target_id == name_id: - next_operand.target.id = self.variable() - self.variables_overwrite[self.scope][name_id] = next_operand # type: ignore[assignment] - - next_res, next_expl = self.visit(next_operand) - if isinstance(next_operand, ast.Compare | ast.BoolOp): - next_expl = f"({next_expl})" - results.append(next_res) - sym = BINOP_MAP[op.__class__] - syms.append(ast.Constant(sym)) - expl = f"{left_expl} {sym} {next_expl}" - expls.append(ast.Constant(expl)) - res_expr = ast.copy_location(ast.Compare(left_res, [op], [next_res]), comp) - self.statements.append(ast.Assign([store_names[i]], res_expr)) - left_res, left_expl = next_res, next_expl - # Use pytest.assertion.util._reprcompare if that's available. - expl_call = self.helper( - "_call_reprcompare", - ast.Tuple(syms, ast.Load()), - ast.Tuple(load_names, ast.Load()), - ast.Tuple(expls, ast.Load()), - ast.Tuple(results, ast.Load()), - ) - if len(comp.ops) > 1: - res: ast.expr = ast.BoolOp(ast.And(), load_names) - else: - res = load_names[0] - - return res, self.explanation_param(self.pop_format_context(expl_call)) - - -def try_makedirs(cache_dir: Path) -> bool: - """Attempt to create the given directory and sub-directories exist. - - Returns True if successful or if it already exists. - """ - try: - os.makedirs(cache_dir, exist_ok=True) - except (FileNotFoundError, NotADirectoryError, FileExistsError): - # One of the path components was not a directory: - # - we're in a zip file - # - it is a file - return False - except PermissionError: - return False - except OSError as e: - # as of now, EROFS doesn't have an equivalent OSError-subclass - # - # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not - # implemented" for a read-only error - if e.errno in {errno.EROFS, errno.ENOSYS}: - return False - raise - return True - - -def get_cache_dir(file_path: Path) -> Path: - """Return the cache directory to write .pyc files for the given .py file path.""" - if sys.pycache_prefix: - # given: - # prefix = '/tmp/pycs' - # path = '/home/user/proj/test_app.py' - # we want: - # '/tmp/pycs/home/user/proj' - return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) - else: - # classic pycache directory - return file_path.parent / "__pycache__" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/truncate.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/truncate.py deleted file mode 100644 index d62ca33..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/truncate.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Utilities for truncating assertion output. - -Current default behaviour is to truncate assertion explanations at -terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. -""" - -from __future__ import annotations - -from _pytest.compat import running_on_ci -from _pytest.config import Config -from _pytest.nodes import Item - - -DEFAULT_MAX_LINES = 8 -DEFAULT_MAX_CHARS = DEFAULT_MAX_LINES * 80 -USAGE_MSG = "use '-vv' to show" - - -def truncate_if_required(explanation: list[str], item: Item) -> list[str]: - """Truncate this assertion explanation if the given test item is eligible.""" - should_truncate, max_lines, max_chars = _get_truncation_parameters(item) - if should_truncate: - return _truncate_explanation( - explanation, - max_lines=max_lines, - max_chars=max_chars, - ) - return explanation - - -def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]: - """Return the truncation parameters related to the given item, as (should truncate, max lines, max chars).""" - # We do not need to truncate if one of conditions is met: - # 1. Verbosity level is 2 or more; - # 2. Test is being run in CI environment; - # 3. Both truncation_limit_lines and truncation_limit_chars - # .ini parameters are set to 0 explicitly. - max_lines = item.config.getini("truncation_limit_lines") - max_lines = int(max_lines if max_lines is not None else DEFAULT_MAX_LINES) - - max_chars = item.config.getini("truncation_limit_chars") - max_chars = int(max_chars if max_chars is not None else DEFAULT_MAX_CHARS) - - verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - - should_truncate = verbose < 2 and not running_on_ci() - should_truncate = should_truncate and (max_lines > 0 or max_chars > 0) - - return should_truncate, max_lines, max_chars - - -def _truncate_explanation( - input_lines: list[str], - max_lines: int, - max_chars: int, -) -> list[str]: - """Truncate given list of strings that makes up the assertion explanation. - - Truncates to either max_lines, or max_chars - whichever the input reaches - first, taking the truncation explanation into account. The remaining lines - will be replaced by a usage message. - - If max_chars=0, no truncation by character count is performed. - If max_lines=0, no truncation by line count is performed. - - When this function is launched we know max_lines > 0 or max_chars > 0 - because _get_truncation_parameters was called first. - """ - # The length of the truncation explanation depends on the number of lines - # removed but is at least 68 characters: - # The real value is - # 64 (for the base message: - # '...\n...Full output truncated (1 line hidden), use '-vv' to show")' - # ) - # + 1 (for plural) - # + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1) - # + 3 for the '...' added to the truncated line - # But if there's more than 100 lines it's very likely that we're going to - # truncate, so we don't need the exact value using log10. - tolerable_max_chars = ( - max_chars + 70 # 64 + 1 (for plural) + 2 (for '99') + 3 for '...' - ) - # The truncation explanation add two lines to the output - if max_lines == 0 or len(input_lines) <= max_lines + 2: - if max_chars == 0 or sum(len(s) for s in input_lines) <= tolerable_max_chars: - return input_lines - truncated_explanation = input_lines - else: - # Truncate first to max_lines, and then truncate to max_chars if necessary - truncated_explanation = input_lines[:max_lines] - # We reevaluate the need to truncate chars following removal of some lines - need_to_truncate_char = ( - max_chars > 0 - and sum(len(e) for e in truncated_explanation) > tolerable_max_chars - ) - if need_to_truncate_char: - truncated_explanation = _truncate_by_char_count( - truncated_explanation, max_chars - ) - # Something was truncated, adding '...' at the end to show that - truncated_explanation[-1] += "..." - truncated_line_count = ( - len(input_lines) - len(truncated_explanation) + int(need_to_truncate_char) - ) - return [ - *truncated_explanation, - "", - f"...Full output truncated ({truncated_line_count} line" - f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", - ] - - -def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: - # Find point at which input length exceeds total allowed length - iterated_char_count = 0 - for iterated_index, input_line in enumerate(input_lines): - if iterated_char_count + len(input_line) > max_chars: - break - iterated_char_count += len(input_line) - - # Create truncated explanation with modified final line - truncated_result = input_lines[:iterated_index] - final_line = input_lines[iterated_index] - if final_line: - final_line_truncate_point = max_chars - iterated_char_count - final_line = final_line[:final_line_truncate_point] - truncated_result.append(final_line) - return truncated_result diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/util.py b/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/util.py deleted file mode 100644 index 5e5ef54..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/assertion/util.py +++ /dev/null @@ -1,215 +0,0 @@ -# mypy: allow-untyped-defs -"""Utilities for assertion debugging.""" - -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Iterator -from collections.abc import Sequence -from typing import Literal -from unicodedata import normalize - -from _pytest import outcomes -import _pytest._code -from _pytest._io.saferepr import saferepr -from _pytest._io.saferepr import saferepr_unlimited -from _pytest.assertion._compare_any import _compare_eq_any -from _pytest.assertion._compare_set import SET_COMPARISON_FUNCTIONS -from _pytest.assertion._guards import isset -from _pytest.assertion._guards import istext -from _pytest.assertion._typing import _AssertionTextDiffStyle -from _pytest.assertion._typing import _HighlightFunc -from _pytest.assertion.compare_text import _notin_text -from _pytest.assertion.highlight import dummy_highlighter as dummy_highlighter -from _pytest.config import Config -from _pytest.config import UsageError - - -# The _reprcompare attribute on the util module is used by the new assertion -# interpretation code and assertion rewriter to detect this plugin was -# loaded and in turn call the hooks defined here as part of the -# DebugInterpreter. -_reprcompare: Callable[[str, object, object], str | None] | None = None - -# Works similarly as _reprcompare attribute. Is populated with the hook call -# when pytest_runtest_setup is called. -_assertion_pass: Callable[[int, str, str], None] | None = None - -# Config object which is assigned during pytest_runtest_protocol. -_config: Config | None = None - -ASSERTION_TEXT_DIFF_STYLE_INI = "assertion_text_diff_style" -ASSERTION_TEXT_DIFF_STYLE_NDIFF: Literal["ndiff"] = "ndiff" -ASSERTION_TEXT_DIFF_STYLE_BLOCK: Literal["block"] = "block" -ASSERTION_TEXT_DIFF_STYLE_CHOICES = ( - ASSERTION_TEXT_DIFF_STYLE_NDIFF, - ASSERTION_TEXT_DIFF_STYLE_BLOCK, -) - - -def get_assertion_text_diff_style(config: Config) -> _AssertionTextDiffStyle: - style = str(config.getini(ASSERTION_TEXT_DIFF_STYLE_INI)) - match style: - case "ndiff" | "block": - return style - case _: - choices = ", ".join( - repr(choice) for choice in ASSERTION_TEXT_DIFF_STYLE_CHOICES - ) - raise UsageError( - f"{ASSERTION_TEXT_DIFF_STYLE_INI} must be one of {choices}; got {style!r}" - ) - - -def validate_assertion_text_diff_style(config: Config) -> None: - get_assertion_text_diff_style(config) - - -def format_explanation(explanation: str) -> str: - r"""Format an explanation. - - Normally all embedded newlines are escaped, however there are - three exceptions: \n{, \n} and \n~. The first two are intended - cover nested explanations, see function and attribute explanations - for examples (.visit_Call(), visit_Attribute()). The last one is - for when one explanation needs to span multiple lines, e.g. when - displaying diffs. - """ - lines = _split_explanation(explanation) - result = _format_lines(lines) - return "\n".join(result) - - -def _split_explanation(explanation: str) -> list[str]: - r"""Return a list of individual lines in the explanation. - - This will return a list of lines split on '\n{', '\n}' and '\n~'. - Any other newlines will be escaped and appear in the line as the - literal '\n' characters. - """ - raw_lines = (explanation or "").split("\n") - lines = [raw_lines[0]] - for values in raw_lines[1:]: - if values and values[0] in ["{", "}", "~", ">"]: - lines.append(values) - else: - lines[-1] += "\\n" + values - return lines - - -def _format_lines(lines: Sequence[str]) -> list[str]: - """Format the individual lines. - - This will replace the '{', '}' and '~' characters of our mini formatting - language with the proper 'where ...', 'and ...' and ' + ...' text, taking - care of indentation along the way. - - Return a list of formatted lines. - """ - result = list(lines[:1]) - stack = [0] - stackcnt = [0] - for line in lines[1:]: - if line.startswith("{"): - if stackcnt[-1]: - s = "and " - else: - s = "where " - stack.append(len(result)) - stackcnt[-1] += 1 - stackcnt.append(0) - result.append(" +" + " " * (len(stack) - 1) + s + line[1:]) - elif line.startswith("}"): - stack.pop() - stackcnt.pop() - result[stack[-1]] += line[1:] - else: - assert line[0] in ["~", ">"] - stack[-1] += 1 - indent = len(stack) if line.startswith("~") else len(stack) - 1 - result.append(" " * indent + line[1:]) - assert len(stack) == 1 - return result - - -def assertrepr_compare( - op: str, - left: object, - right: object, - *, - verbose: int, - highlighter: _HighlightFunc, - assertion_text_diff_style: _AssertionTextDiffStyle, -) -> Iterator[str]: - """Yield specialised explanations for some operators/operands. - - The first line yielded is always the summary (``left op right``); - subsequent lines are the detailed explanation. Yields nothing when no - specialised explanation applies, which lets consumers map an empty - iterator to "no explanation" without materialising anything. - - The iterator is lazy on purpose: a streaming consumer can stop pulling - lines as soon as it has enough to show, so an enormous diff doesn't - have to be built in full just to be thrown away. - """ - # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. - # See issue #3246. - use_ascii = ( - isinstance(left, str) - and isinstance(right, str) - and normalize("NFD", left) == normalize("NFD", right) - ) - - if verbose > 1: - left_repr = saferepr_unlimited(left, use_ascii=use_ascii) - right_repr = saferepr_unlimited(right, use_ascii=use_ascii) - else: - # XXX: "15 chars indentation" is wrong - # ("E AssertionError: assert "); should use term width. - maxsize = ( - 80 - 15 - len(op) - 2 - ) // 2 # 15 chars indentation, 1 space around op - - left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii) - right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) - - summary = f"{left_repr} {op} {right_repr}" - - try: - if op == "==": - source = _compare_eq_any( - left, - right, - highlighter, - verbose, - assertion_text_diff_style, - ) - elif op == "not in" and istext(left) and istext(right): - source = _notin_text(left, right, verbose) - elif op in {"!=", ">=", "<=", ">", "<"} and isset(left) and isset(right): - source = SET_COMPARISON_FUNCTIONS[op](left, right, highlighter, verbose) - else: - source = iter(()) - - # Only yield the summary if there is a detailed explanation. - # Make sure there's a separating empty line after the summary. - summary_yielded = False - for line in source: - if not summary_yielded: - yield summary - if line != "": - yield "" - summary_yielded = True - yield line - except outcomes.Exit: - raise - except Exception: - repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash() - if not summary_yielded: - yield summary - yield "" - summary_yielded = True - yield ( - f"(pytest_assertion plugin: representation of details failed: {repr_crash}." - ) - yield " Probably an object has a faulty __repr__.)" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/cacheprovider.py b/tests/venv2/lib/python3.11/site-packages/_pytest/cacheprovider.py deleted file mode 100644 index 6bcac1a..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/cacheprovider.py +++ /dev/null @@ -1,640 +0,0 @@ -# mypy: allow-untyped-defs -"""Implementation of the cache provider.""" - -# This plugin was not named "cache" to avoid conflicts with the external -# pytest-cache version. -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Iterable -import dataclasses -import errno -import json -import os -from pathlib import Path -import shutil -import tempfile -from typing import final - -from .pathlib import resolve_from_str -from .pathlib import rm_rf -from .reports import CollectReport -from _pytest import nodes -from _pytest._io import TerminalWriter -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import FixtureRequest -from _pytest.main import Session -from _pytest.nodes import Directory -from _pytest.nodes import File -from _pytest.reports import TestReport - - -CACHEDIR_FILES: dict[str, bytes] = { - "README.md": b"""\ -# pytest cache directory # - -This directory contains data from the pytest's cache plugin, -which provides the `--lf` and `--ff` options, as well as the `cache` fixture. - -**Do not** commit this to version control. - -See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information. -""", - ".gitignore": b"# Created by pytest automatically.\n*\n", - "CACHEDIR.TAG": b"""\ -Signature: 8a477f597d28d172789f06886806bc55 -# This file is a cache directory tag created by pytest. -# For information about cache directory tags, see: -# https://bford.info/cachedir/spec.html -""", -} - - -def _make_cachedir(target: Path) -> None: - """Create the pytest cache directory atomically with supporting files. - - Creates a temporary directory with README.md, .gitignore, and CACHEDIR.TAG, - then atomically renames it to the target location. If another process wins - the race, the temporary directory is cleaned up. - """ - target.parent.mkdir(parents=True, exist_ok=True) - path = Path(tempfile.mkdtemp(prefix="pytest-cache-files-", dir=target.parent)) - try: - # Reset permissions to the default, see #12308. - # Note: there's no way to get the current umask atomically, eek. - umask = os.umask(0o022) - os.umask(umask) - path.chmod(0o777 - umask) - - for name, content in CACHEDIR_FILES.items(): - path.joinpath(name).write_bytes(content) - - path.rename(target) - except OSError as e: - # If 2 concurrent pytests both race to the rename, the loser - # gets "Directory not empty" from the rename. In this case, - # everything is handled so just continue after cleanup. - # On Windows, the error is a FileExistsError which translates to EEXIST. - if e.errno not in (errno.ENOTEMPTY, errno.EEXIST): - raise - finally: - shutil.rmtree(path, ignore_errors=True) - - -@final -@dataclasses.dataclass -class Cache: - """Instance of the `cache` fixture.""" - - _cachedir: Path = dataclasses.field(repr=False) - _config: Config = dataclasses.field(repr=False) - - # Sub-directory under cache-dir for directories created by `mkdir()`. - _CACHE_PREFIX_DIRS = "d" - - # Sub-directory under cache-dir for values created by `set()`. - _CACHE_PREFIX_VALUES = "v" - - def __init__( - self, cachedir: Path, config: Config, *, _ispytest: bool = False - ) -> None: - check_ispytest(_ispytest) - self._cachedir = cachedir - self._config = config - - @classmethod - def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: - """Create the Cache instance for a Config. - - :meta private: - """ - check_ispytest(_ispytest) - cachedir = cls.cache_dir_from_config(config, _ispytest=True) - if config.getoption("cacheclear") and cachedir.is_dir(): - cls.clear_cache(cachedir, _ispytest=True) - return cls(cachedir, config, _ispytest=True) - - @classmethod - def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None: - """Clear the sub-directories used to hold cached directories and values. - - :meta private: - """ - check_ispytest(_ispytest) - for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES): - d = cachedir / prefix - if d.is_dir(): - rm_rf(d) - - @staticmethod - def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path: - """Get the path to the cache directory for a Config. - - :meta private: - """ - check_ispytest(_ispytest) - return resolve_from_str(config.getini("cache_dir"), config.rootpath) - - def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None: - """Issue a cache warning. - - :meta private: - """ - check_ispytest(_ispytest) - import warnings - - from _pytest.warning_types import PytestCacheWarning - - warnings.warn( - PytestCacheWarning(fmt.format(**args) if args else fmt), - self._config.hook, - stacklevel=3, - ) - - def _mkdir(self, path: Path) -> None: - self._ensure_cache_dir_and_supporting_files() - path.mkdir(exist_ok=True, parents=True) - - def mkdir(self, name: str) -> Path: - """Return a directory path object with the given name. - - If the directory does not yet exist, it will be created. You can use - it to manage files to e.g. store/retrieve database dumps across test - sessions. - - .. versionadded:: 7.0 - - :param name: - Must be a string not containing a ``/`` separator. - Make sure the name contains your plugin or application - identifiers to prevent clashes with other cache users. - """ - path = Path(name) - if len(path.parts) > 1: - raise ValueError("name is not allowed to contain path separators") - res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) - self._mkdir(res) - return res - - def _getvaluepath(self, key: str) -> Path: - return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key)) - - def get(self, key: str, default): - """Return the cached value for the given key. - - If no value was yet cached or the value cannot be read, the specified - default is returned. - - :param key: - Must be a ``/`` separated value. Usually the first - name is the name of your plugin or your application. - :param default: - The value to return in case of a cache-miss or invalid cache value. - """ - path = self._getvaluepath(key) - try: - with path.open("r", encoding="UTF-8") as f: - return json.load(f) - except (ValueError, OSError): - return default - - def set(self, key: str, value: object) -> None: - """Save value for the given key. - - :param key: - Must be a ``/`` separated value. Usually the first - name is the name of your plugin or your application. - :param value: - Must be of any combination of basic python types, - including nested types like lists of dictionaries. - """ - path = self._getvaluepath(key) - try: - self._mkdir(path.parent) - except OSError as exc: - self.warn( - f"could not create cache path {path}: {exc}", - _ispytest=True, - ) - return - data = json.dumps(value, ensure_ascii=False, indent=2) - try: - f = path.open("w", encoding="UTF-8") - except OSError as exc: - self.warn( - f"cache could not write path {path}: {exc}", - _ispytest=True, - ) - else: - with f: - f.write(data) - - def _ensure_cache_dir_and_supporting_files(self) -> None: - """Create the cache dir and its supporting files.""" - if not self._cachedir.is_dir(): - _make_cachedir(self._cachedir) - - -class LFPluginCollWrapper: - def __init__(self, lfplugin: LFPlugin) -> None: - self.lfplugin = lfplugin - self._collected_at_least_one_failure = False - - @hookimpl(wrapper=True) - def pytest_make_collect_report( - self, collector: nodes.Collector - ) -> Generator[None, CollectReport, CollectReport]: - res = yield - if isinstance(collector, Session | Directory): - # Sort any lf-paths to the beginning. - lf_paths = self.lfplugin._last_failed_paths - - # Use stable sort to prioritize last failed. - def sort_key(node: nodes.Item | nodes.Collector) -> bool: - return node.path in lf_paths - - res.result = sorted( - res.result, - key=sort_key, - reverse=True, - ) - - elif isinstance(collector, File): - if collector.path in self.lfplugin._last_failed_paths: - result = res.result - lastfailed = self.lfplugin.lastfailed - - # Only filter with known failures. - if not self._collected_at_least_one_failure: - if not any(x.nodeid in lastfailed for x in result): - return res - self.lfplugin.config.pluginmanager.register( - LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip" - ) - self._collected_at_least_one_failure = True - - session = collector.session - result[:] = [ - x - for x in result - if x.nodeid in lastfailed - # Include any passed arguments (not trivial to filter). - or session.isinitpath(x.path) - # Keep all sub-collectors. - or isinstance(x, nodes.Collector) - ] - - return res - - -class LFPluginCollSkipfiles: - def __init__(self, lfplugin: LFPlugin) -> None: - self.lfplugin = lfplugin - - @hookimpl - def pytest_make_collect_report( - self, collector: nodes.Collector - ) -> CollectReport | None: - if isinstance(collector, File): - if collector.path not in self.lfplugin._last_failed_paths: - self.lfplugin._skipped_files += 1 - - return CollectReport( - collector.nodeid, "passed", longrepr=None, result=[] - ) - return None - - -class LFPlugin: - """Plugin which implements the --lf (run last-failing) option.""" - - def __init__(self, config: Config) -> None: - self.config = config - active_keys = "lf", "failedfirst" - self.active = any(config.getoption(key) for key in active_keys) - assert config.cache - self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) - self._previously_failed_count: int | None = None - self._report_status: str | None = None - self._skipped_files = 0 # count skipped files during collection due to --lf - - if config.getoption("lf"): - self._last_failed_paths = self.get_last_failed_paths() - config.pluginmanager.register( - LFPluginCollWrapper(self), "lfplugin-collwrapper" - ) - - def get_last_failed_paths(self) -> set[Path]: - """Return a set with all Paths of the previously failed nodeids and - their parents.""" - rootpath = self.config.rootpath - result = set() - for nodeid in self.lastfailed: - path = rootpath / nodeid.split("::")[0] - result.add(path) - result.update(path.parents) - return {x for x in result if x.exists()} - - def pytest_report_collectionfinish(self) -> str | None: - if self.active and self.config.get_verbosity() >= 0: - return f"run-last-failure: {self._report_status}" - return None - - def pytest_runtest_logreport(self, report: TestReport) -> None: - if (report.when == "call" and report.passed) or report.skipped: - self.lastfailed.pop(report.nodeid, None) - elif report.failed: - self.lastfailed[report.nodeid] = True - - def pytest_collectreport(self, report: CollectReport) -> None: - passed = report.outcome in ("passed", "skipped") - if passed: - if report.nodeid in self.lastfailed: - self.lastfailed.pop(report.nodeid) - self.lastfailed.update((item.nodeid, True) for item in report.result) - else: - self.lastfailed[report.nodeid] = True - - @hookimpl(wrapper=True, tryfirst=True) - def pytest_collection_modifyitems( - self, config: Config, items: list[nodes.Item] - ) -> Generator[None]: - res = yield - - if not self.active: - return res - - if self.lastfailed: - previously_failed = [] - previously_passed = [] - for item in items: - if item.nodeid in self.lastfailed: - previously_failed.append(item) - else: - previously_passed.append(item) - self._previously_failed_count = len(previously_failed) - - if not previously_failed: - # Running a subset of all tests with recorded failures - # only outside of it. - self._report_status = ( - f"{len(self.lastfailed)} known failures not in selected tests" - ) - else: - if self.config.getoption("lf"): - items[:] = previously_failed - config.hook.pytest_deselected(items=previously_passed) - else: # --failedfirst - items[:] = previously_failed + previously_passed - - noun = "failure" if self._previously_failed_count == 1 else "failures" - suffix = " first" if self.config.getoption("failedfirst") else "" - self._report_status = ( - f"rerun previous {self._previously_failed_count} {noun}{suffix}" - ) - - if self._skipped_files > 0: - files_noun = "file" if self._skipped_files == 1 else "files" - self._report_status += f" (skipped {self._skipped_files} {files_noun})" - else: - self._report_status = "no previously failed tests, " - if self.config.getoption("last_failed_no_failures") == "none": - self._report_status += "deselecting all items." - config.hook.pytest_deselected(items=items[:]) - items[:] = [] - else: - self._report_status += "not deselecting items." - - return res - - def pytest_sessionfinish(self, session: Session) -> None: - config = self.config - if config.getoption("cacheshow") or hasattr(config, "workerinput"): - return - - assert config.cache is not None - saved_lastfailed = config.cache.get("cache/lastfailed", {}) - if saved_lastfailed != self.lastfailed: - config.cache.set("cache/lastfailed", self.lastfailed) - - -class NFPlugin: - """Plugin which implements the --nf (run new-first) option.""" - - def __init__(self, config: Config) -> None: - self.config = config - self.active = config.option.newfirst - assert config.cache is not None - self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) - - @hookimpl(wrapper=True, tryfirst=True) - def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> Generator[None]: - res = yield - - if self.active: - new_items: dict[str, nodes.Item] = {} - other_items: dict[str, nodes.Item] = {} - for item in items: - if item.nodeid not in self.cached_nodeids: - new_items[item.nodeid] = item - else: - other_items[item.nodeid] = item - - items[:] = self._get_increasing_order( - new_items.values() - ) + self._get_increasing_order(other_items.values()) - self.cached_nodeids.update(new_items) - else: - self.cached_nodeids.update(item.nodeid for item in items) - - return res - - def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: - return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) - - def pytest_sessionfinish(self) -> None: - config = self.config - if config.getoption("cacheshow") or hasattr(config, "workerinput"): - return - - if config.getoption("collectonly"): - return - - assert config.cache is not None - config.cache.set("cache/nodeids", sorted(self.cached_nodeids)) - - -def pytest_addoption(parser: Parser) -> None: - """Add command-line options for cache functionality. - - :param parser: Parser object to add command-line options to. - """ - group = parser.getgroup("general") - group.addoption( - "--lf", - "--last-failed", - action="store_true", - dest="lf", - help="Rerun only the tests that failed at the last run (or all if none failed)", - ) - group.addoption( - "--ff", - "--failed-first", - action="store_true", - dest="failedfirst", - help="Run all tests, but run the last failures first. " - "This may re-order tests and thus lead to " - "repeated fixture setup/teardown.", - ) - group.addoption( - "--nf", - "--new-first", - action="store_true", - dest="newfirst", - help="Run tests from new files first, then the rest of the tests " - "sorted by file mtime", - ) - group.addoption( - "--cache-show", - action="append", - nargs="?", - dest="cacheshow", - help=( - "Show cache contents, don't perform collection or tests. " - "Optional argument: glob (default: '*')." - ), - ) - group.addoption( - "--cache-clear", - action="store_true", - dest="cacheclear", - help="Remove all cache contents at start of test run", - ) - cache_dir_default = ".pytest_cache" - if "TOX_ENV_DIR" in os.environ: - cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default) - parser.addini("cache_dir", default=cache_dir_default, help="Cache directory path") - group.addoption( - "--lfnf", - "--last-failed-no-failures", - action="store", - dest="last_failed_no_failures", - choices=("all", "none"), - default="all", - help="With ``--lf``, determines whether to execute tests when there " - "are no previously (known) failures or when no " - "cached ``lastfailed`` data was found. " - "``all`` (the default) runs the full test suite again. " - "``none`` just emits a message about no known failures and exits successfully.", - ) - - -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - if config.option.cacheshow and not config.option.help: - from _pytest.main import wrap_session - - return wrap_session(config, cacheshow) - return None - - -@hookimpl(tryfirst=True) -def pytest_configure(config: Config) -> None: - """Configure cache system and register related plugins. - - Creates the Cache instance and registers the last-failed (LFPlugin) - and new-first (NFPlugin) plugins with the plugin manager. - - :param config: pytest configuration object. - """ - config.cache = Cache.for_config(config, _ispytest=True) - config.pluginmanager.register(LFPlugin(config), "lfplugin") - config.pluginmanager.register(NFPlugin(config), "nfplugin") - - -@fixture -def cache(request: FixtureRequest) -> Cache: - """Return a cache object that can persist state between testing sessions. - - cache.get(key, default) - cache.set(key, value) - - Keys must be ``/`` separated strings, where the first part is usually the - name of your plugin or application to avoid clashes with other cache users. - - Values can be any object handled by the json stdlib module. - """ - assert request.config.cache is not None - return request.config.cache - - -def pytest_report_header(config: Config) -> str | None: - """Display cachedir with --cache-show and if non-default.""" - if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": - assert config.cache is not None - cachedir = config.cache._cachedir - # TODO: evaluate generating upward relative paths - # starting with .., ../.. if sensible - - try: - displaypath = cachedir.relative_to(config.rootpath) - except ValueError: - displaypath = cachedir - return f"cachedir: {displaypath}" - return None - - -def cacheshow(config: Config, session: Session) -> int: - """Display cache contents when --cache-show is used. - - Shows cached values and directories matching the specified glob pattern - (default: '*'). Displays cache location, cached test results, and - any cached directories created by plugins. - - :param config: pytest configuration object. - :param session: pytest session object. - :returns: Exit code (0 for success). - """ - from pprint import pformat - - assert config.cache is not None - - tw = TerminalWriter() - tw.line("cachedir: " + str(config.cache._cachedir)) - if not config.cache._cachedir.is_dir(): - tw.line("cache is empty") - return 0 - - glob = config.option.cacheshow[0] - if glob is None: - glob = "*" - - dummy = object() - basedir = config.cache._cachedir - vdir = basedir / Cache._CACHE_PREFIX_VALUES - tw.sep("-", f"cache values for {glob!r}") - for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): - key = str(valpath.relative_to(vdir)) - val = config.cache.get(key, dummy) - if val is dummy: - tw.line(f"{key} contains unreadable content, will be ignored") - else: - tw.line(f"{key} contains:") - for line in pformat(val).splitlines(): - tw.line(" " + line) - - ddir = basedir / Cache._CACHE_PREFIX_DIRS - if ddir.is_dir(): - contents = sorted(ddir.rglob(glob)) - tw.sep("-", f"cache directories for {glob!r}") - for p in contents: - # if p.is_dir(): - # print("%s/" % p.relative_to(basedir)) - if p.is_file(): - key = str(p.relative_to(basedir)) - tw.line(f"{key} is a file of length {p.stat().st_size}") - return 0 diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/capture.py b/tests/venv2/lib/python3.11/site-packages/_pytest/capture.py deleted file mode 100644 index f9f8f9e..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/capture.py +++ /dev/null @@ -1,1151 +0,0 @@ -# mypy: allow-untyped-defs -"""Per-test stdout/stderr capturing mechanism.""" - -from __future__ import annotations - -import abc -import collections -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -import contextlib -import io -from io import UnsupportedOperation -import os -import sys -from tempfile import TemporaryFile -from types import TracebackType -from typing import Any -from typing import AnyStr -from typing import BinaryIO -from typing import cast -from typing import Final -from typing import final -from typing import Generic -from typing import Literal -from typing import NamedTuple -from typing import TextIO -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from typing_extensions import Self - -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import SubRequest -from _pytest.nodes import Collector -from _pytest.nodes import File -from _pytest.nodes import Item -from _pytest.reports import CollectReport - - -_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group.addoption( - "--capture", - action="store", - default="fd", - metavar="method", - choices=["fd", "sys", "no", "tee-sys"], - help="Per-test capturing method: one of fd|sys|no|tee-sys", - ) - group._addoption( # private to use reserved lower-case short option - "-s", - action="store_const", - const="no", - dest="capture", - help="Shortcut for --capture=no", - ) - - -def _colorama_workaround() -> None: - """Ensure colorama is imported so that it attaches to the correct stdio - handles on Windows. - - colorama uses the terminal on import time. So if something does the - first import of colorama while I/O capture is active, colorama will - fail in various ways. - """ - if sys.platform.startswith("win32"): - try: - import colorama # noqa: F401 - except ImportError: - pass - - -def _readline_workaround() -> None: - """Ensure readline is imported early so it attaches to the correct stdio handles. - - This isn't a problem with the default GNU readline implementation, but in - some configurations, Python uses libedit instead (on macOS, and for prebuilt - binaries such as used by uv). - - In theory this is only needed if readline.backend == "libedit", but the - workaround consists of importing readline here, so we already worked around - the issue by the time we could check if we need to. - """ - try: - import readline # noqa: F401 - except ImportError: - pass - - -def _windowsconsoleio_workaround(stream: TextIO) -> None: - """Workaround for Windows Unicode console handling. - - Python 3.6 implemented Unicode console handling for Windows. This works - by reading/writing to the raw console handle using - ``{Read,Write}ConsoleW``. - - The problem is that we are going to ``dup2`` over the stdio file - descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the - handles used by Python to write to the console. Though there is still some - weirdness and the console handle seems to only be closed randomly and not - on the first call to ``CloseHandle``, or maybe it gets reopened with the - same handle value when we suspend capturing. - - The workaround in this case will reopen stdio with a different fd which - also means a different handle by replicating the logic in - "Py_lifecycle.c:initstdio/create_stdio". - - :param stream: - In practice ``sys.stdout`` or ``sys.stderr``, but given - here as parameter for unittesting purposes. - - See https://github.com/pytest-dev/py/issues/103. - """ - if not sys.platform.startswith("win32") or hasattr(sys, "pypy_version_info"): - return - - # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). - if not hasattr(stream, "buffer"): # type: ignore[unreachable,unused-ignore] - return - - raw_stdout = stream.buffer.raw if hasattr(stream.buffer, "raw") else stream.buffer - - if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined,unused-ignore] - return - - def _reopen_stdio(f, mode): - if not hasattr(stream.buffer, "raw") and mode[0] == "w": - buffering = 0 - else: - buffering = -1 - - return io.TextIOWrapper( - open(os.dup(f.fileno()), mode, buffering), - f.encoding, - f.errors, - f.newlines, - f.line_buffering, - ) - - sys.stdin = _reopen_stdio(sys.stdin, "rb") - sys.stdout = _reopen_stdio(sys.stdout, "wb") - sys.stderr = _reopen_stdio(sys.stderr, "wb") - - -@hookimpl(wrapper=True) -def pytest_load_initial_conftests(early_config: Config) -> Generator[None]: - ns = early_config.known_args_namespace - if ns.capture == "fd": - _windowsconsoleio_workaround(sys.stdout) - _colorama_workaround() - _readline_workaround() - pluginmanager = early_config.pluginmanager - capman = CaptureManager(ns.capture) - pluginmanager.register(capman, "capturemanager") - - # Make sure that capturemanager is properly reset at final shutdown. - early_config.add_cleanup(capman.stop_global_capturing) - - # Finally trigger conftest loading but while capturing (issue #93). - capman.start_global_capturing() - try: - try: - yield - finally: - capman.suspend_global_capture() - except BaseException: - out, err = capman.read_global_capture() - sys.stdout.write(out) - sys.stderr.write(err) - raise - - -# IO Helpers. - - -class EncodedFile(io.TextIOWrapper): - __slots__ = () - - @property - def name(self) -> str: - # Ensure that file.name is a string. Workaround for a Python bug - # fixed in >=3.7.4: https://bugs.python.org/issue36015 - return repr(self.buffer) - - @property - def mode(self) -> str: - # TextIOWrapper doesn't expose a mode, but at least some of our - # tests check it. - assert hasattr(self.buffer, "mode") - return cast(str, self.buffer.mode.replace("b", "")) - - -class CaptureIO(io.TextIOWrapper): - def __init__(self) -> None: - super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True) - - def getvalue(self) -> str: - assert isinstance(self.buffer, io.BytesIO) - return self.buffer.getvalue().decode("UTF-8") - - -class TeeCaptureIO(CaptureIO): - def __init__(self, other: TextIO) -> None: - self._other = other - super().__init__() - - def write(self, s: str) -> int: - super().write(s) - return self._other.write(s) - - -class DontReadFromInput(TextIO): - @property - def encoding(self) -> str: - assert sys.__stdin__ is not None - return sys.__stdin__.encoding - - def read(self, size: int = -1) -> str: - raise OSError( - "pytest: reading from stdin while output is captured! Consider using `-s`." - ) - - readline = read - - def __next__(self) -> str: - return self.readline() - - def readlines(self, hint: int | None = -1) -> list[str]: - raise OSError( - "pytest: reading from stdin while output is captured! Consider using `-s`." - ) - - def __iter__(self) -> Iterator[str]: - return self - - def fileno(self) -> int: - raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") - - def flush(self) -> None: - raise UnsupportedOperation("redirected stdin is pseudofile, has no flush()") - - def isatty(self) -> bool: - return False - - def close(self) -> None: - pass - - def readable(self) -> bool: - return False - - def seek(self, offset: int, whence: int = 0) -> int: - raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)") - - def seekable(self) -> bool: - return False - - def tell(self) -> int: - raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - - def truncate(self, size: int | None = None) -> int: - raise UnsupportedOperation("cannot truncate stdin") - - def write(self, data: str) -> int: - raise UnsupportedOperation("cannot write to stdin") - - def writelines(self, lines: Iterable[str]) -> None: - raise UnsupportedOperation("Cannot write to stdin") - - def writable(self) -> bool: - return False - - def __enter__(self) -> Self: - return self - - def __exit__( - self, - type: type[BaseException] | None, - value: BaseException | None, - traceback: TracebackType | None, - ) -> None: - pass - - @property - def buffer(self) -> BinaryIO: - # The str/bytes doesn't actually matter in this type, so OK to fake. - return self # type: ignore[return-value] - - -# Capture classes. - - -class CaptureBase(abc.ABC, Generic[AnyStr]): - EMPTY_BUFFER: AnyStr - - @abc.abstractmethod - def __init__(self, fd: int) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def start(self) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def done(self) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def suspend(self) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def resume(self) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def writeorg(self, data: AnyStr) -> None: - raise NotImplementedError() - - @abc.abstractmethod - def snap(self) -> AnyStr: - raise NotImplementedError() - - -patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} - - -class NoCapture(CaptureBase[str]): - EMPTY_BUFFER = "" - - def __init__(self, fd: int) -> None: - pass - - def start(self) -> None: - pass - - def done(self) -> None: - pass - - def suspend(self) -> None: - pass - - def resume(self) -> None: - pass - - def snap(self) -> str: - return "" - - def writeorg(self, data: str) -> None: - pass - - -class SysCaptureBase(CaptureBase[AnyStr]): - def __init__( - self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False - ) -> None: - name = patchsysdict[fd] - self._old: TextIO = getattr(sys, name) - self.name = name - if tmpfile is None: - if name == "stdin": - tmpfile = DontReadFromInput() - else: - tmpfile = CaptureIO() if not tee else TeeCaptureIO(self._old) - self.tmpfile = tmpfile - self._state = "initialized" - - def repr(self, class_name: str) -> str: - return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( - class_name, - self.name, - (hasattr(self, "_old") and repr(self._old)) or "", - self._state, - self.tmpfile, - ) - - def __repr__(self) -> str: - return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( - self.__class__.__name__, - self.name, - (hasattr(self, "_old") and repr(self._old)) or "", - self._state, - self.tmpfile, - ) - - def _assert_state(self, op: str, states: tuple[str, ...]) -> None: - assert self._state in states, ( - "cannot {} in state {!r}: expected one of {}".format( - op, self._state, ", ".join(states) - ) - ) - - def start(self) -> None: - self._assert_state("start", ("initialized",)) - setattr(sys, self.name, self.tmpfile) - self._state = "started" - - def done(self) -> None: - self._assert_state("done", ("initialized", "started", "suspended", "done")) - if self._state == "done": - return - setattr(sys, self.name, self._old) - del self._old - self.tmpfile.close() - self._state = "done" - - def suspend(self) -> None: - self._assert_state("suspend", ("started", "suspended")) - setattr(sys, self.name, self._old) - self._state = "suspended" - - def resume(self) -> None: - self._assert_state("resume", ("started", "suspended")) - if self._state == "started": - return - setattr(sys, self.name, self.tmpfile) - self._state = "started" - - -class SysCaptureBinary(SysCaptureBase[bytes]): - EMPTY_BUFFER = b"" - - def snap(self) -> bytes: - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.buffer.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - - def writeorg(self, data: bytes) -> None: - self._assert_state("writeorg", ("started", "suspended")) - self._old.flush() - self._old.buffer.write(data) - self._old.buffer.flush() - - -class SysCapture(SysCaptureBase[str]): - EMPTY_BUFFER = "" - - def snap(self) -> str: - self._assert_state("snap", ("started", "suspended")) - assert isinstance(self.tmpfile, CaptureIO) - res = self.tmpfile.getvalue() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - - def writeorg(self, data: str) -> None: - self._assert_state("writeorg", ("started", "suspended")) - self._old.write(data) - self._old.flush() - - -class FDCaptureBase(CaptureBase[AnyStr]): - def __init__(self, targetfd: int) -> None: - self.targetfd = targetfd - - try: - os.fstat(targetfd) - except OSError: - # FD capturing is conceptually simple -- create a temporary file, - # redirect the FD to it, redirect back when done. But when the - # target FD is invalid it throws a wrench into this lovely scheme. - # - # Tests themselves shouldn't care if the FD is valid, FD capturing - # should work regardless of external circumstances. So falling back - # to just sys capturing is not a good option. - # - # Further complications are the need to support suspend() and the - # possibility of FD reuse (e.g. the tmpfile getting the very same - # target FD). The following approach is robust, I believe. - self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) - os.dup2(self.targetfd_invalid, targetfd) - else: - self.targetfd_invalid = None - self.targetfd_save = os.dup(targetfd) - - if targetfd == 0: - self.tmpfile = open(os.devnull, encoding="utf-8") - self.syscapture: CaptureBase[str] = SysCapture(targetfd) - else: - self.tmpfile = EncodedFile( - TemporaryFile(buffering=0), - encoding="utf-8", - errors="replace", - newline="", - write_through=True, - ) - if targetfd in patchsysdict: - self.syscapture = SysCapture(targetfd, self.tmpfile) - else: - self.syscapture = NoCapture(targetfd) - - self._state = "initialized" - - def __repr__(self) -> str: - return ( - f"<{self.__class__.__name__} {self.targetfd} oldfd={self.targetfd_save} " - f"_state={self._state!r} tmpfile={self.tmpfile!r}>" - ) - - def _assert_state(self, op: str, states: tuple[str, ...]) -> None: - assert self._state in states, ( - "cannot {} in state {!r}: expected one of {}".format( - op, self._state, ", ".join(states) - ) - ) - - def start(self) -> None: - """Start capturing on targetfd using memorized tmpfile.""" - self._assert_state("start", ("initialized",)) - os.dup2(self.tmpfile.fileno(), self.targetfd) - self.syscapture.start() - self._state = "started" - - def done(self) -> None: - """Stop capturing, restore streams, return original capture file, - seeked to position zero.""" - self._assert_state("done", ("initialized", "started", "suspended", "done")) - if self._state == "done": - return - os.dup2(self.targetfd_save, self.targetfd) - os.close(self.targetfd_save) - if self.targetfd_invalid is not None: - if self.targetfd_invalid != self.targetfd: - os.close(self.targetfd) - os.close(self.targetfd_invalid) - self.syscapture.done() - self.tmpfile.close() - self._state = "done" - - def suspend(self) -> None: - self._assert_state("suspend", ("started", "suspended")) - if self._state == "suspended": - return - self.syscapture.suspend() - os.dup2(self.targetfd_save, self.targetfd) - self._state = "suspended" - - def resume(self) -> None: - self._assert_state("resume", ("started", "suspended")) - if self._state == "started": - return - self.syscapture.resume() - os.dup2(self.tmpfile.fileno(), self.targetfd) - self._state = "started" - - -class FDCaptureBinary(FDCaptureBase[bytes]): - """Capture IO to/from a given OS-level file descriptor. - - snap() produces `bytes`. - """ - - EMPTY_BUFFER = b"" - - def snap(self) -> bytes: - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.buffer.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res # type: ignore[return-value] - - def writeorg(self, data: bytes) -> None: - """Write to original file descriptor.""" - self._assert_state("writeorg", ("started", "suspended")) - os.write(self.targetfd_save, data) - - -class FDCapture(FDCaptureBase[str]): - """Capture IO to/from a given OS-level file descriptor. - - snap() produces text. - """ - - EMPTY_BUFFER = "" - - def snap(self) -> str: - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - - def writeorg(self, data: str) -> None: - """Write to original file descriptor.""" - self._assert_state("writeorg", ("started", "suspended")) - # XXX use encoding of original stream - os.write(self.targetfd_save, data.encode("utf-8")) - - -# MultiCapture - - -# Generic NamedTuple only supported since Python 3.11. -if sys.version_info >= (3, 11) or TYPE_CHECKING: - - @final - class CaptureResult(NamedTuple, Generic[AnyStr]): - """The result of :method:`caplog.readouterr() `.""" - - out: AnyStr - err: AnyStr - -else: - - class CaptureResult( - collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024 - Generic[AnyStr], - ): - """The result of :method:`caplog.readouterr() `.""" - - __slots__ = () - - -class MultiCapture(Generic[AnyStr]): - _state = None - _in_suspended = False - - def __init__( - self, - in_: CaptureBase[AnyStr] | None, - out: CaptureBase[AnyStr] | None, - err: CaptureBase[AnyStr] | None, - ) -> None: - self.in_: CaptureBase[AnyStr] | None = in_ - self.out: CaptureBase[AnyStr] | None = out - self.err: CaptureBase[AnyStr] | None = err - - def __repr__(self) -> str: - return ( - f"" - ) - - def start_capturing(self) -> None: - self._state = "started" - if self.in_: - self.in_.start() - if self.out: - self.out.start() - if self.err: - self.err.start() - - def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: - """Pop current snapshot out/err capture and flush to orig streams.""" - out, err = self.readouterr() - if out: - assert self.out is not None - self.out.writeorg(out) - if err: - assert self.err is not None - self.err.writeorg(err) - return out, err - - def suspend_capturing(self, in_: bool = False) -> None: - self._state = "suspended" - if self.out: - self.out.suspend() - if self.err: - self.err.suspend() - if in_ and self.in_: - self.in_.suspend() - self._in_suspended = True - - def resume_capturing(self) -> None: - self._state = "started" - if self.out: - self.out.resume() - if self.err: - self.err.resume() - if self._in_suspended: - assert self.in_ is not None - self.in_.resume() - self._in_suspended = False - - def stop_capturing(self) -> None: - """Stop capturing and reset capturing streams.""" - if self._state == "stopped": - raise ValueError("was already stopped") - self._state = "stopped" - if self.out: - self.out.done() - if self.err: - self.err.done() - if self.in_: - self.in_.done() - - def is_started(self) -> bool: - """Whether actively capturing -- not suspended or stopped.""" - return self._state == "started" - - def readouterr(self) -> CaptureResult[AnyStr]: - out = self.out.snap() if self.out else "" - err = self.err.snap() if self.err else "" - # TODO: This type error is real, need to fix. - return CaptureResult(out, err) # type: ignore[arg-type] - - -def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]: - if method == "fd": - return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) - elif method == "sys": - return MultiCapture(in_=SysCapture(0), out=SysCapture(1), err=SysCapture(2)) - elif method == "no": - return MultiCapture(in_=None, out=None, err=None) - elif method == "tee-sys": - return MultiCapture( - in_=None, out=SysCapture(1, tee=True), err=SysCapture(2, tee=True) - ) - raise ValueError(f"unknown capturing method: {method!r}") - - -# CaptureManager and CaptureFixture - - -class CaptureManager: - """The capture plugin. - - Manages that the appropriate capture method is enabled/disabled during - collection and each test phase (setup, call, teardown). After each of - those points, the captured output is obtained and attached to the - collection/runtest report. - - There are two levels of capture: - - * global: enabled by default and can be suppressed by the ``-s`` - option. This is always enabled/disabled during collection and each test - phase. - - * fixture: when a test function or one of its fixture depend on the - ``capsys`` or ``capfd`` fixtures. In this case special handling is - needed to ensure the fixtures take precedence over the global capture. - """ - - def __init__(self, method: _CaptureMethod) -> None: - self._method: Final = method - self._global_capturing: MultiCapture[str] | None = None - self._capture_fixture: CaptureFixture[Any] | None = None - - def __repr__(self) -> str: - return ( - f"" - ) - - def is_capturing(self) -> str | bool: - if self.is_globally_capturing(): - return "global" - if self._capture_fixture: - return f"fixture {self._capture_fixture.request.fixturename}" - return False - - # Global capturing control - - def is_globally_capturing(self) -> bool: - return self._method != "no" - - def start_global_capturing(self) -> None: - assert self._global_capturing is None - self._global_capturing = _get_multicapture(self._method) - self._global_capturing.start_capturing() - - def stop_global_capturing(self) -> None: - if self._global_capturing is not None: - self._global_capturing.pop_outerr_to_orig() - self._global_capturing.stop_capturing() - self._global_capturing = None - - def resume_global_capture(self) -> None: - # During teardown of the python process, and on rare occasions, capture - # attributes can be `None` while trying to resume global capture. - if self._global_capturing is not None: - self._global_capturing.resume_capturing() - - def suspend_global_capture(self, in_: bool = False) -> None: - if self._global_capturing is not None: - self._global_capturing.suspend_capturing(in_=in_) - - def suspend(self, in_: bool = False) -> None: - # Need to undo local capsys-et-al if it exists before disabling global capture. - self.suspend_fixture() - self.suspend_global_capture(in_) - - def resume(self) -> None: - self.resume_global_capture() - self.resume_fixture() - - def read_global_capture(self) -> CaptureResult[str]: - assert self._global_capturing is not None - return self._global_capturing.readouterr() - - # Fixture Control - - def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None: - if self._capture_fixture: - current_fixture = self._capture_fixture.request.fixturename - requested_fixture = capture_fixture.request.fixturename - capture_fixture.request.raiseerror( - f"cannot use {requested_fixture} and {current_fixture} at the same time" - ) - self._capture_fixture = capture_fixture - - def unset_fixture(self) -> None: - self._capture_fixture = None - - def activate_fixture(self) -> None: - """If the current item is using ``capsys`` or ``capfd``, activate - them so they take precedence over the global capture.""" - if self._capture_fixture: - self._capture_fixture._start() - - def deactivate_fixture(self) -> None: - """Deactivate the ``capsys`` or ``capfd`` fixture of this item, if any.""" - if self._capture_fixture: - self._capture_fixture.close() - - def suspend_fixture(self) -> None: - if self._capture_fixture: - self._capture_fixture._suspend() - - def resume_fixture(self) -> None: - if self._capture_fixture: - self._capture_fixture._resume() - - # Helper context managers - - @contextlib.contextmanager - def global_and_fixture_disabled(self) -> Generator[None]: - """Context manager to temporarily disable global and current fixture capturing.""" - do_fixture = self._capture_fixture and self._capture_fixture._is_started() - if do_fixture: - self.suspend_fixture() - do_global = self._global_capturing and self._global_capturing.is_started() - if do_global: - self.suspend_global_capture() - try: - yield - finally: - if do_global: - self.resume_global_capture() - if do_fixture: - self.resume_fixture() - - @contextlib.contextmanager - def item_capture(self, when: str, item: Item) -> Generator[None]: - self.resume_global_capture() - self.activate_fixture() - try: - yield - finally: - self.deactivate_fixture() - self.suspend_global_capture(in_=False) - - out, err = self.read_global_capture() - item.add_report_section(when, "stdout", out) - item.add_report_section(when, "stderr", err) - - # Hooks - - @hookimpl(wrapper=True) - def pytest_make_collect_report( - self, collector: Collector - ) -> Generator[None, CollectReport, CollectReport]: - if isinstance(collector, File): - self.resume_global_capture() - try: - rep = yield - finally: - self.suspend_global_capture() - out, err = self.read_global_capture() - if out: - rep.sections.append(("Captured stdout", out)) - if err: - rep.sections.append(("Captured stderr", err)) - else: - rep = yield - return rep - - @hookimpl(wrapper=True) - def pytest_runtest_setup(self, item: Item) -> Generator[None]: - with self.item_capture("setup", item): - return (yield) - - @hookimpl(wrapper=True) - def pytest_runtest_call(self, item: Item) -> Generator[None]: - with self.item_capture("call", item): - return (yield) - - @hookimpl(wrapper=True) - def pytest_runtest_teardown(self, item: Item) -> Generator[None]: - with self.item_capture("teardown", item): - return (yield) - - @hookimpl(tryfirst=True) - def pytest_keyboard_interrupt(self) -> None: - self.stop_global_capturing() - - @hookimpl(tryfirst=True) - def pytest_internalerror(self) -> None: - self.stop_global_capturing() - - -class CaptureFixture(Generic[AnyStr]): - """Object returned by the :fixture:`capsys`, :fixture:`capsysbinary`, - :fixture:`capfd` and :fixture:`capfdbinary` fixtures.""" - - def __init__( - self, - captureclass: type[CaptureBase[AnyStr]], - request: SubRequest, - *, - config: dict[str, Any] | None = None, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self.captureclass: type[CaptureBase[AnyStr]] = captureclass - self.request = request - self._config = config if config else {} - self._capture: MultiCapture[AnyStr] | None = None - self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER - self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER - - def _start(self) -> None: - if self._capture is None: - self._capture = MultiCapture( - in_=None, - out=self.captureclass(1, **self._config), - err=self.captureclass(2, **self._config), - ) - self._capture.start_capturing() - - def close(self) -> None: - if self._capture is not None: - if self._config.get("tee"): - # When tee is enabled, output was already written to the - # original stream in real-time by TeeCaptureIO. Using - # pop_outerr_to_orig() would write it a second time via - # writeorg(), causing doubled output (see #13784). - out, err = self._capture.readouterr() - else: - out, err = self._capture.pop_outerr_to_orig() - self._captured_out += out - self._captured_err += err - self._capture.stop_capturing() - self._capture = None - - def readouterr(self) -> CaptureResult[AnyStr]: - """Read and return the captured output so far, resetting the internal - buffer. - - :returns: - The captured content as a namedtuple with ``out`` and ``err`` - string attributes. - """ - captured_out, captured_err = self._captured_out, self._captured_err - if self._capture is not None: - out, err = self._capture.readouterr() - captured_out += out - captured_err += err - self._captured_out = self.captureclass.EMPTY_BUFFER - self._captured_err = self.captureclass.EMPTY_BUFFER - return CaptureResult(captured_out, captured_err) - - def _suspend(self) -> None: - """Suspend this fixture's own capturing temporarily.""" - if self._capture is not None: - self._capture.suspend_capturing() - - def _resume(self) -> None: - """Resume this fixture's own capturing temporarily.""" - if self._capture is not None: - self._capture.resume_capturing() - - def _is_started(self) -> bool: - """Whether actively capturing -- not disabled or closed.""" - if self._capture is not None: - return self._capture.is_started() - return False - - @contextlib.contextmanager - def disabled(self) -> Generator[None]: - """Temporarily disable capturing while inside the ``with`` block.""" - capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( - "capturemanager" - ) - with capmanager.global_and_fixture_disabled(): - yield - - -# The fixtures. - - -@fixture -def capsys(request: SubRequest) -> Generator[CaptureFixture[str]]: - r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. - - The captured output is made available via ``capsys.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capsys): - print("hello") - captured = capsys.readouterr() - assert captured.out == "hello\n" - """ - capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture(SysCapture, request, _ispytest=True) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() - - -@fixture -def capteesys(request: SubRequest) -> Generator[CaptureFixture[str]]: - r"""Enable simultaneous text capturing and pass-through of writes - to ``sys.stdout`` and ``sys.stderr`` as defined by ``--capture=``. - - - The captured output is made available via ``capteesys.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - - The output is also passed-through, allowing it to be "live-printed", - reported, or both as defined by ``--capture=``. - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capteesys): - print("hello") - captured = capteesys.readouterr() - assert captured.out == "hello\n" - """ - capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture( - SysCapture, request, config=dict(tee=True), _ispytest=True - ) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() - - -@fixture -def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: - r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. - - The captured output is made available via ``capsysbinary.readouterr()`` - method calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``bytes`` objects. - - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_output(capsysbinary): - print("hello") - captured = capsysbinary.readouterr() - assert captured.out == b"hello\n" - """ - capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture(SysCaptureBinary, request, _ispytest=True) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() - - -@fixture -def capfd(request: SubRequest) -> Generator[CaptureFixture[str]]: - r"""Enable text capturing of writes to file descriptors ``1`` and ``2``. - - The captured output is made available via ``capfd.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfd): - os.system('echo "hello"') - captured = capfd.readouterr() - assert captured.out == "hello\n" - """ - capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture(FDCapture, request, _ispytest=True) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() - - -@fixture -def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: - r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. - - The captured output is made available via ``capfd.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``byte`` objects. - - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfdbinary): - os.system('echo "hello"') - captured = capfdbinary.readouterr() - assert captured.out == b"hello\n" - - """ - capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture(FDCaptureBinary, request, _ispytest=True) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/compat.py b/tests/venv2/lib/python3.11/site-packages/_pytest/compat.py deleted file mode 100644 index d3b2a46..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/compat.py +++ /dev/null @@ -1,329 +0,0 @@ -# mypy: allow-untyped-defs -"""Python version compatibility code and random general utilities.""" - -from __future__ import annotations - -from collections.abc import Callable -import enum -import functools -import inspect -from inspect import Parameter -from inspect import Signature -import os -from pathlib import Path -import sys -from typing import Any -from typing import Final -from typing import NoReturn -from typing import TYPE_CHECKING - -import py - - -if sys.version_info >= (3, 14): - from annotationlib import Format - - -#: constant to prepare valuing pylib path replacements/lazy proxies later on -# intended for removal in pytest 8.0 or 9.0 - -# fmt: off -# intentional space to create a fake difference for the verification -LEGACY_PATH = py.path. local -# fmt: on - - -def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: - """Internal wrapper to prepare lazy proxies for legacy_path instances""" - return LEGACY_PATH(path) - - -# fmt: off -# Singleton type for NOTSET, as described in: -# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions -class NotSetType(enum.Enum): - token = 0 -NOTSET: Final = NotSetType.token -# fmt: on - - -def iscoroutinefunction(func: object) -> bool: - """Return True if func is a coroutine function (a function defined with async - def syntax, and doesn't contain yield), or a function decorated with - @asyncio.coroutine. - - Note: copied and modified from Python 3.5's builtin coroutines.py to avoid - importing asyncio directly, which in turns also initializes the "logging" - module as a side-effect (see issue #8). - """ - return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False) - - -def is_async_function(func: object) -> bool: - """Return True if the given function seems to be an async function or - an async generator.""" - return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) - - -def signature(obj: Callable[..., Any]) -> Signature: - """Return signature without evaluating annotations.""" - if sys.version_info >= (3, 14): - return inspect.signature(obj, annotation_format=Format.STRING) - return inspect.signature(obj) - - -def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: - function = get_real_func(function) - fn = Path(inspect.getfile(function)) - lineno = function.__code__.co_firstlineno - if curdir is not None: - try: - relfn = fn.relative_to(curdir) - except ValueError: - pass - else: - return f"{relfn}:{lineno + 1}" - return f"{fn}:{lineno + 1}" - - -def num_mock_patch_args(function) -> int: - """Return number of arguments used up by mock arguments (if any).""" - patchings = getattr(function, "patchings", None) - if not patchings: - return 0 - - mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object()) - ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object()) - - return len( - [ - p - for p in patchings - if not p.attribute_name - and (p.new is mock_sentinel or p.new is ut_mock_sentinel) - ] - ) - - -def getfuncargnames( - function: Callable[..., object], - *, - name: str = "", - cls: type | None = None, -) -> tuple[str, ...]: - """Return the names of a function's mandatory arguments. - - Should return the names of all function arguments that: - * Aren't bound to an instance or type as in instance or class methods. - * Don't have default values. - * Aren't bound with functools.partial. - * Aren't replaced with mocks. - - The cls arguments indicate that the function should be treated as a bound - method even though it's not unless the function is a static method. - - The name parameter should be the original name in which the function was collected. - """ - # TODO(RonnyPfannschmidt): This function should be refactored when we - # revisit fixtures. The fixture mechanism should ask the node for - # the fixture names, and not try to obtain directly from the - # function object well after collection has occurred. - - # The parameters attribute of a Signature object contains an - # ordered mapping of parameter names to Parameter instances. This - # creates a tuple of the names of the parameters that don't have - # defaults. - try: - parameters = signature(function).parameters.values() - except (ValueError, TypeError) as e: - from _pytest.outcomes import fail - - fail( - f"Could not determine arguments of {function!r}: {e}", - pytrace=False, - ) - - arg_names = tuple( - p.name - for p in parameters - if ( - p.kind is Parameter.POSITIONAL_OR_KEYWORD - or p.kind is Parameter.KEYWORD_ONLY - ) - and p.default is Parameter.empty - ) - if not name: - name = function.__name__ - - # If this function should be treated as a bound method even though - # it's passed as an unbound method or function, and its first parameter - # wasn't defined as positional only, remove the first parameter name. - if not any(p.kind is Parameter.POSITIONAL_ONLY for p in parameters) and ( - # Not using `getattr` because we don't want to resolve the staticmethod. - # Not using `cls.__dict__` because we want to check the entire MRO. - cls - and not isinstance( - inspect.getattr_static(cls, name, default=None), staticmethod - ) - ): - arg_names = arg_names[1:] - # Remove any names that will be replaced with mocks. - if hasattr(function, "__wrapped__"): - arg_names = arg_names[num_mock_patch_args(function) :] - return arg_names - - -def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]: - # Note: this code intentionally mirrors the code at the beginning of - # getfuncargnames, to get the arguments which were excluded from its result - # because they had default values. - return tuple( - p.name - for p in signature(function).parameters.values() - if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) - and p.default is not Parameter.empty - ) - - -_non_printable_ascii_translate_table = { - i: f"\\x{i:02x}" for i in range(128) if i not in range(32, 127) -} -_non_printable_ascii_translate_table.update( - {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"} -) - - -def ascii_escaped(val: bytes | str) -> str: - r"""If val is pure ASCII, return it as an str, otherwise, escape - bytes objects into a sequence of escaped bytes: - - b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6' - - and escapes strings into a sequence of escaped unicode ids, e.g.: - - r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' - - Note: - The obvious "v.decode('unicode-escape')" will return - valid UTF-8 unicode if it finds them in bytes, but we - want to return escaped bytes for any byte, even if they match - a UTF-8 string. - """ - if isinstance(val, bytes): - ret = val.decode("ascii", "backslashreplace") - else: - ret = val.encode("unicode_escape").decode("ascii") - return ret.translate(_non_printable_ascii_translate_table) - - -def get_real_func(obj): - """Get the real function object of the (possibly) wrapped object by - :func:`functools.wraps`, or :func:`functools.partial`.""" - obj = inspect.unwrap(obj) - - if isinstance(obj, functools.partial): - obj = obj.func - return obj - - -def getimfunc(func): - try: - return func.__func__ - except AttributeError: - return func - - -def safe_getattr(object: Any, name: str, default: Any) -> Any: - """Like getattr but return default upon any Exception or any OutcomeException. - - Attribute access can potentially fail for 'evil' Python objects. - See issue #214. - It catches OutcomeException because of #2490 (issue #580), new outcomes - are derived from BaseException instead of Exception (for more details - check #2707). - """ - from _pytest.outcomes import TEST_OUTCOME - - try: - return getattr(object, name, default) - except TEST_OUTCOME: - return default - - -def safe_isclass(obj: object) -> bool: - """Ignore any exception via isinstance on Python 3.""" - try: - return inspect.isclass(obj) - except Exception: - return False - - -def get_user_id() -> int | None: - """Return the current process's real user id or None if it could not be - determined. - - :return: The user id or None if it could not be determined. - """ - # mypy follows the version and platform checking expectation of PEP 484: - # https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks - # Containment checks are too complex for mypy v1.5.0 and cause failure. - if sys.platform == "win32" or sys.platform == "emscripten": - # win32 does not have a getuid() function. - # Emscripten has a return 0 stub. - return None - else: - # On other platforms, a return value of -1 is assumed to indicate that - # the current process's real user id could not be determined. - ERROR = -1 - uid = os.getuid() - return uid if uid != ERROR else None - - -if sys.version_info >= (3, 11): - from typing import assert_never -else: - - def assert_never(value: NoReturn) -> NoReturn: - assert False, f"Unhandled value: {value} ({type(value).__name__})" - - -class CallableBool: - """ - A bool-like object that can also be called, returning its true/false value. - - Used for backwards compatibility in cases where something was supposed to be a method - but was implemented as a simple attribute by mistake (see `TerminalReporter.isatty`). - - Do not use in new code. - """ - - def __init__(self, value: bool) -> None: - self._value = value - - def __bool__(self) -> bool: - return self._value - - def __call__(self) -> bool: - return self._value - - -def running_on_ci() -> bool: - """Check if we're currently running on a CI system.""" - # Only enable CI mode if one of these env variables is defined and non-empty. - # Note: review `regendoc` tox env in case this list is changed. - env_vars = ["CI", "BUILD_NUMBER"] - return any(os.environ.get(var) for var in env_vars) - - -if sys.version_info >= (3, 13): - from warnings import deprecated as deprecated -else: - if TYPE_CHECKING: - from typing_extensions import deprecated as deprecated - else: - - def deprecated(msg, /, *, category=None, stacklevel=1): - def decorator(func): - return func - - return decorator diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/config/__init__.py deleted file mode 100644 index 96b3dd6..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__init__.py +++ /dev/null @@ -1,2246 +0,0 @@ -# mypy: allow-untyped-defs -"""Command line options, config-file and conftest.py processing.""" - -from __future__ import annotations - -import argparse -import builtins -import collections.abc -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import MutableMapping -from collections.abc import Sequence -import contextlib -import copy -import dataclasses -import enum -from functools import lru_cache -import glob -import importlib -import importlib.metadata -import inspect -import os -import pathlib -import re -import shlex -import sys -from textwrap import dedent -import types -from types import FunctionType -from typing import Any -from typing import cast -from typing import Final -from typing import final -from typing import IO -from typing import TextIO -from typing import TYPE_CHECKING -import warnings - -from pluggy import HookimplMarker -from pluggy import HookimplOpts -from pluggy import HookspecMarker -from pluggy import HookspecOpts -from pluggy import PluginManager - -from .exceptions import PrintHelp as PrintHelp -from .exceptions import UsageError as UsageError -from .findpaths import ConfigDict -from .findpaths import ConfigValue -from .findpaths import determine_setup -from .findpaths import parse_override_ini -from _pytest import __version__ -import _pytest._code -from _pytest._code import ExceptionInfo -from _pytest._code import filter_traceback -from _pytest._code.code import TracebackStyle -from _pytest._io import TerminalWriter -from _pytest.compat import assert_never -from _pytest.compat import deprecated -from _pytest.compat import NOTSET -from _pytest.config.argparsing import Argument -from _pytest.config.argparsing import FILE_OR_DIR -from _pytest.config.argparsing import Parser -import _pytest.deprecated -import _pytest.hookspec -from _pytest.outcomes import fail -from _pytest.outcomes import Skipped -from _pytest.pathlib import absolutepath -from _pytest.pathlib import bestrelpath -from _pytest.pathlib import import_path -from _pytest.pathlib import ImportMode -from _pytest.pathlib import resolve_package_path -from _pytest.pathlib import safe_exists -from _pytest.stash import Stash -from _pytest.warning_types import PytestConfigWarning -from _pytest.warning_types import warn_explicit_for - - -if TYPE_CHECKING: - from _pytest.assertion.rewrite import AssertionRewritingHook - from _pytest.cacheprovider import Cache - from _pytest.terminal import TerminalReporter - -_PluggyPlugin = object -"""A type to represent plugin objects. - -Plugins can be any namespace, so we can't narrow it down much, but we use an -alias to make the intent clear. - -Ideally this type would be provided by pluggy itself. -""" - - -hookimpl = HookimplMarker("pytest") -hookspec = HookspecMarker("pytest") - - -@final -class ExitCode(enum.IntEnum): - """Encodes the valid exit codes by pytest. - - Currently users and plugins may supply other exit codes as well. - - .. versionadded:: 5.0 - """ - - #: Tests passed. - OK = 0 - #: Tests failed. - TESTS_FAILED = 1 - #: pytest was interrupted. - INTERRUPTED = 2 - #: An internal error got in the way. - INTERNAL_ERROR = 3 - #: pytest was misused. - USAGE_ERROR = 4 - #: pytest couldn't find tests. - NO_TESTS_COLLECTED = 5 - #: All tests pass, but maximum number of warnings exceeded. - MAX_WARNINGS_ERROR = 6 - - __module__ = "pytest" - - -class ConftestImportFailure(Exception): - def __init__( - self, - path: pathlib.Path, - *, - cause: Exception, - ) -> None: - self.path = path - self.cause = cause - - def __str__(self) -> str: - return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" - - -def filter_traceback_for_conftest_import_failure( - entry: _pytest._code.TracebackEntry, -) -> bool: - """Filter tracebacks entries which point to pytest internals or importlib. - - Make a special case for importlib because we use it to import test modules and conftest files - in _pytest.pathlib.import_path. - """ - return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) - - -def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None: - exc_info = ExceptionInfo.from_exception(e.cause) - tw = TerminalWriter(file) - tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) - exc_info.traceback = exc_info.traceback.filter( - filter_traceback_for_conftest_import_failure - ) - exc_repr = ( - exc_info.getrepr(style="short", chain=False) - if exc_info.traceback - else exc_info.exconly() - ) - formatted_tb = str(exc_repr) - for line in formatted_tb.splitlines(): - tw.line(line.rstrip(), red=True) - - -def print_usage_error(e: UsageError, file: TextIO) -> None: - tw = TerminalWriter(file) - for msg in e.args: - tw.line(f"ERROR: {msg}\n", red=True) - - -def _get_prog_name(argv: Sequence[str]) -> str: - """Determine the CLI program name from the argument vector. - - :param argv: The argument vector (typically ``sys.argv``). - :returns: ``"python -m pytest"`` when invoked via ``python -m``, - ``"pytest"`` otherwise. - """ - argv0 = argv[0] if argv else "" - if os.path.basename(argv0) == "__main__.py": - return "python -m pytest" - return "pytest" - - -def main( - args: list[str] | os.PathLike[str] | None = None, - plugins: Sequence[str | _PluggyPlugin] | None = None, -) -> int | ExitCode: - """Perform an in-process test run. - - :param args: - List of command line arguments. If `None` or not given, defaults to reading - arguments directly from the process command line (:data:`sys.argv`). - :param plugins: List of plugin objects to be auto-registered during initialization. - - :returns: An exit code. - """ - return _main(args=args, plugins=plugins, prog="pytest.main()") - - -def _main( - *, - args: list[str] | os.PathLike[str] | None = None, - plugins: Sequence[str | _PluggyPlugin] | None = None, - prog: str, -) -> int | ExitCode: - # Handle a single `--version`/`-V` argument early to avoid starting up the entire pytest infrastructure. - new_args = sys.argv[1:] if args is None else args - if ( - isinstance(new_args, Sequence) - and (new_args.count("--version") + new_args.count("-V")) == 1 - ): - sys.stdout.write(f"pytest {__version__}\n") - return ExitCode.OK - - old_pytest_version = os.environ.get("PYTEST_VERSION") - try: - os.environ["PYTEST_VERSION"] = __version__ - try: - config = _prepareconfig(new_args, plugins, prog=prog) - except ConftestImportFailure as e: - print_conftest_import_error(e, file=sys.stderr) - return ExitCode.USAGE_ERROR - - try: - ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) - try: - return ExitCode(ret) - except ValueError: - return ret - finally: - config._ensure_unconfigure() - except UsageError as e: - print_usage_error(e, file=sys.stderr) - return ExitCode.USAGE_ERROR - finally: - if old_pytest_version is None: - os.environ.pop("PYTEST_VERSION", None) - else: - os.environ["PYTEST_VERSION"] = old_pytest_version - - -def _console_main() -> int: - """The CLI entry point of pytest (internal). - - This is the real implementation used by entry points and ``__main__.py``. - """ - # https://docs.python.org/3/library/signal.html#note-on-sigpipe - try: - code = _main(prog=_get_prog_name(sys.argv)) - sys.stdout.flush() - return code - except BrokenPipeError: - # Python flushes standard streams on exit; redirect remaining output - # to devnull to avoid another BrokenPipeError at shutdown - devnull = os.open(os.devnull, os.O_WRONLY) - os.dup2(devnull, sys.stdout.fileno()) - return 1 # Python exits with error code 1 on EPIPE - - -def console_main() -> int: - """The CLI entry point of pytest. - - .. deprecated:: 9.1 - This function is slated for removal in pytest 10. - It is not meant for programmable use; use :func:`pytest.main` instead. - """ - import warnings - - from _pytest.deprecated import CONSOLE_MAIN - - warnings.warn(CONSOLE_MAIN, stacklevel=2) - return _console_main() - - -class cmdline: # compatibility namespace - main = staticmethod(main) - - -def filename_arg(path: str, optname: str) -> str: - """Argparse type validator for filename arguments. - - :path: Path of filename. - :optname: Name of the option. - """ - if os.path.isdir(path): - raise UsageError(f"{optname} must be a filename, given: {path}") - return path - - -def directory_arg(path: str, optname: str) -> str: - """Argparse type validator for directory arguments. - - :path: Path of directory. - :optname: Name of the option. - """ - if not os.path.isdir(path): - raise UsageError(f"{optname} must be a directory, given: {path}") - return path - - -# Plugins that cannot be disabled via "-p no:X" currently. -essential_plugins = ( - "mark", - "main", - "runner", - "fixtures", - "helpconfig", # Provides -p. -) - -default_plugins = ( - *essential_plugins, - "python", - "terminal", - "debugging", - "unittest", - "capture", - "skipping", - "legacypath", - "tmpdir", - "monkeypatch", - "recwarn", - "pastebin", - "assertion", - "junitxml", - "doctest", - "cacheprovider", - "setuponly", - "setupplan", - "stepwise", - "unraisableexception", - "threadexception", - "warnings", - "logging", - "reports", - "faulthandler", - "subtests", -) - -builtin_plugins = { - *default_plugins, - "pytester", - "pytester_assertions", - "terminalprogress", -} - - -def get_config( - args: Iterable[str] | None = None, - plugins: Sequence[str | _PluggyPlugin] | None = None, - *, - prog: str | None = None, -) -> Config: - # Subsequent calls to main will create a fresh instance. - pluginmanager = PytestPluginManager() - invocation_params = Config.InvocationParams( - args=args or (), - plugins=plugins, - dir=pathlib.Path.cwd(), - ) - config = Config(pluginmanager, invocation_params=invocation_params, prog=prog) - - if invocation_params.args: - # Handle any "-p no:plugin" args. - pluginmanager.consider_preparse(invocation_params.args, exclude_only=True) - - for spec in default_plugins: - pluginmanager.import_plugin(spec) - - return config - - -def get_plugin_manager() -> PytestPluginManager: - """Obtain a new instance of the - :py:class:`pytest.PytestPluginManager`, with default plugins - already loaded. - - This function can be used by integration with other tools, like hooking - into pytest to run tests into an IDE. - """ - return get_config().pluginmanager - - -def _prepareconfig( - args: list[str] | os.PathLike[str], - plugins: Sequence[str | _PluggyPlugin] | None = None, - *, - prog: str | None = None, -) -> Config: - if isinstance(args, os.PathLike): - args = [os.fspath(args)] - elif not isinstance(args, list): - msg = ( # type:ignore[unreachable] - "`args` parameter expected to be a list of strings, got: {!r} (type: {})" - ) - raise TypeError(msg.format(args, type(args))) - - initial_config = get_config(args, plugins, prog=prog) - pluginmanager = initial_config.pluginmanager - try: - if plugins: - for plugin in plugins: - if isinstance(plugin, str): - pluginmanager.consider_pluginarg(plugin) - else: - pluginmanager.register(plugin) - config: Config = pluginmanager.hook.pytest_cmdline_parse( - pluginmanager=pluginmanager, args=args - ) - return config - except BaseException: - initial_config._ensure_unconfigure() - raise - - -def _get_directory(path: pathlib.Path) -> pathlib.Path: - """Get the directory of a path - itself if already a directory.""" - if path.is_file(): - return path.parent - else: - return path - - -def _get_legacy_hook_marks( - method: Any, - hook_type: str, - opt_names: tuple[str, ...], -) -> dict[str, bool]: - if TYPE_CHECKING: - # abuse typeguard from importlib to avoid massive method type union that's lacking an alias - assert inspect.isroutine(method) - known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} - must_warn: list[str] = [] - opts: dict[str, bool] = {} - for opt_name in opt_names: - opt_attr = getattr(method, opt_name, AttributeError) - if opt_attr is not AttributeError: - must_warn.append(f"{opt_name}={opt_attr}") - opts[opt_name] = True - elif opt_name in known_marks: - must_warn.append(f"{opt_name}=True") - opts[opt_name] = True - else: - opts[opt_name] = False - if must_warn: - hook_opts = ", ".join(must_warn) - message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( - type=hook_type, - fullname=method.__qualname__, - hook_opts=hook_opts, - ) - warn_explicit_for(cast(FunctionType, method), message) - return opts - - -@final -class PytestPluginManager(PluginManager): - """A :py:class:`pluggy.PluginManager ` with - additional pytest-specific functionality: - - * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and - ``pytest_plugins`` global variables found in plugins being loaded. - * ``conftest.py`` loading during start-up. - """ - - def __init__(self) -> None: - from _pytest.assertion import DummyRewriteHook - from _pytest.assertion import RewriteHook - - super().__init__("pytest") - - # -- State related to local conftest plugins. - # All loaded conftest modules. - self._conftest_plugins: set[types.ModuleType] = set() - # All conftest modules applicable for a directory. - # This includes the directory's own conftest modules as well - # as those of its parent directories. - self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} - # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: pathlib.Path | None = None - # If set, conftest loading is skipped. - self._noconftest = False - - # _getconftestmodules()'s call to _get_directory() causes a stat - # storm when it's called potentially thousands of times in a test - # session (#9478), often with the same path, so cache it. - self._get_directory = lru_cache(256)(_get_directory) - - # plugins that were explicitly skipped with pytest.skip - # list of (module name, skip reason) - # previously we would issue a warning when a plugin was skipped, but - # since we refactored warnings as first citizens of Config, they are - # just stored here to be used later. - self.skipped_plugins: list[tuple[str, str]] = [] - - self.add_hookspecs(_pytest.hookspec) - self.register(self) - if os.environ.get("PYTEST_DEBUG"): - err: IO[str] = sys.stderr - encoding: str = getattr(err, "encoding", "utf8") - try: - err = open( - os.dup(err.fileno()), - mode=err.mode, - buffering=1, - encoding=encoding, - ) - except Exception: - pass - self.trace.root.setwriter(err.write) - self.enable_tracing() - - # Config._consider_importhook will set a real object if required. - self.rewrite_hook: RewriteHook = DummyRewriteHook() - # Used to know when we are importing conftests after the pytest_configure stage. - self._configured = False - - def parse_hookimpl_opts( - self, plugin: _PluggyPlugin, name: str - ) -> HookimplOpts | None: - """:meta private:""" - # pytest hooks are always prefixed with "pytest_", - # so we avoid accessing possibly non-readable attributes - # (see issue #1073). - if not name.startswith("pytest_"): - return None - # Ignore names which cannot be hooks. - if name == "pytest_plugins": - return None - - opts = super().parse_hookimpl_opts(plugin, name) - if opts is not None: - return opts - - method = getattr(plugin, name) - # Consider only actual functions for hooks (#3775). - if not inspect.isroutine(method): - return None - # Collect unmarked hooks as long as they have the `pytest_' prefix. - legacy = _get_legacy_hook_marks( - method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") - ) - return cast(HookimplOpts, legacy) - - def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: - """:meta private:""" - opts = super().parse_hookspec_opts(module_or_class, name) - if opts is None: - method = getattr(module_or_class, name) - if name.startswith("pytest_"): - legacy = _get_legacy_hook_marks( - method, "spec", ("firstresult", "historic") - ) - opts = cast(HookspecOpts, legacy) - return opts - - def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: - if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: - warnings.warn( - PytestConfigWarning( - "{} plugin has been merged into the core, " - "please remove it from your requirements.".format( - name.replace("_", "-") - ) - ) - ) - return None - plugin_name = super().register(plugin, name) - if plugin_name is not None: - self.hook.pytest_plugin_registered.call_historic( - kwargs=dict( - plugin=plugin, - plugin_name=plugin_name, - manager=self, - ) - ) - - if isinstance(plugin, types.ModuleType): - self.consider_module(plugin) - return plugin_name - - def getplugin(self, name: str): - # Support deprecated naming because plugins (xdist e.g.) use it. - plugin: _PluggyPlugin | None = self.get_plugin(name) - return plugin - - def hasplugin(self, name: str) -> bool: - """Return whether a plugin with the given name is registered.""" - return bool(self.get_plugin(name)) - - def pytest_configure(self, config: Config) -> None: - """:meta private:""" - # XXX now that the pluginmanager exposes hookimpl(tryfirst...) - # we should remove tryfirst/trylast as markers. - config.addinivalue_line( - "markers", - "tryfirst: mark a hook implementation function such that the " - "plugin machinery will try to call it first/as early as possible. " - "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", - ) - config.addinivalue_line( - "markers", - "trylast: mark a hook implementation function such that the " - "plugin machinery will try to call it last/as late as possible. " - "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", - ) - self._configured = True - - # - # Internal API for local conftest plugin handling. - # - def _set_initial_conftests( - self, - args: Sequence[str | pathlib.Path], - pyargs: bool, - noconftest: bool, - rootpath: pathlib.Path, - confcutdir: pathlib.Path | None, - invocation_dir: pathlib.Path, - importmode: ImportMode | str, - *, - consider_namespace_packages: bool, - ) -> None: - """Load initial conftest files given a preparsed "namespace". - - As conftest files may add their own command line options which have - arguments ('--my-opt somepath') we might get some false positives. - All builtin and 3rd party plugins will have been loaded, however, so - common options will not confuse our logic here. - """ - self._confcutdir = ( - absolutepath(invocation_dir / confcutdir) if confcutdir else None - ) - self._noconftest = noconftest - self._using_pyargs = pyargs - - anchors = [] - for initial_path in args: - path = str(initial_path) - # remove node-id syntax - i = path.find("::") - if i != -1: - path = path[:i] - anchor = absolutepath(invocation_dir / path) - # Ensure we do not break if what appears to be an anchor - # is in fact a very long option (#10169, #11394). - if not safe_exists(anchor): - continue - - anchors.append(anchor) - # Let's also consider test* subdirs. - if anchor.is_dir(): - anchors.extend(x for x in anchor.glob("test*") if x.is_dir()) - if not anchors: - anchors.append(invocation_dir) - anchors.extend(x for x in invocation_dir.glob("test*") if x.is_dir()) - - for anchor in anchors: - self._loadconftestmodules( - anchor, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) - - def _is_in_confcutdir(self, path: pathlib.Path) -> bool: - """Whether to consider the given path to load conftests from.""" - if self._confcutdir is None: - return True - # The semantics here are literally: - # Do not load a conftest if it is found upwards from confcut dir. - # But this is *not* the same as: - # Load only conftests from confcutdir or below. - # At first glance they might seem the same thing, however we do support use cases where - # we want to load conftests that are not found in confcutdir or below, but are found - # in completely different directory hierarchies like packages installed - # in out-of-source trees. - # (see #9767 for a regression where the logic was inverted). - return path not in self._confcutdir.parents - - def _loadconftestmodules( - self, - path: pathlib.Path, - importmode: str | ImportMode, - rootpath: pathlib.Path, - *, - consider_namespace_packages: bool, - ) -> None: - if self._noconftest: - return - - directory = self._get_directory(path) - - # Optimization: avoid repeated searches in the same directory. - # Assumes always called with same importmode and rootpath. - if directory in self._dirpath2confmods: - return - - clist = [] - for parent in reversed((directory, *directory.parents)): - if self._is_in_confcutdir(parent): - conftestpath = parent / "conftest.py" - if conftestpath.is_file(): - mod = self._importconftest( - conftestpath, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) - clist.append(mod) - self._dirpath2confmods[directory] = clist - - def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: - directory = self._get_directory(path) - return self._dirpath2confmods.get(directory, ()) - - def _rget_with_confmod( - self, - name: str, - path: pathlib.Path, - ) -> tuple[types.ModuleType, Any]: - modules = self._getconftestmodules(path) - for mod in reversed(modules): - try: - return mod, getattr(mod, name) - except AttributeError: - continue - raise KeyError(name) - - def _importconftest( - self, - conftestpath: pathlib.Path, - importmode: str | ImportMode, - rootpath: pathlib.Path, - *, - consider_namespace_packages: bool, - ) -> types.ModuleType: - conftestpath_plugin_name = str(conftestpath) - existing = self.get_plugin(conftestpath_plugin_name) - if existing is not None: - return cast(types.ModuleType, existing) - - # conftest.py files there are not in a Python package all have module - # name "conftest", and thus conflict with each other. Clear the existing - # before loading the new one, otherwise the existing one will be - # returned from the module cache. - pkgpath = resolve_package_path(conftestpath) - if pkgpath is None: - try: - del sys.modules[conftestpath.stem] - except KeyError: - pass - - try: - mod = import_path( - conftestpath, - mode=importmode, - root=rootpath, - consider_namespace_packages=consider_namespace_packages, - ) - except Exception as e: - assert e.__traceback__ is not None - raise ConftestImportFailure(conftestpath, cause=e) from e - - self._check_non_top_pytest_plugins(mod, conftestpath) - - self._conftest_plugins.add(mod) - dirpath = conftestpath.parent - if dirpath in self._dirpath2confmods: - for path, mods in self._dirpath2confmods.items(): - if dirpath in path.parents or path == dirpath: - if mod in mods: - raise AssertionError( - f"While trying to load conftest path {conftestpath!s}, " - f"found that the module {mod} is already loaded with path {mod.__file__}. " - "This is not supposed to happen. Please report this issue to pytest." - ) - mods.append(mod) - self.trace(f"loading conftestmodule {mod!r}") - self.consider_conftest(mod, registration_name=conftestpath_plugin_name) - return mod - - def _check_non_top_pytest_plugins( - self, - mod: types.ModuleType, - conftestpath: pathlib.Path, - ) -> None: - if ( - hasattr(mod, "pytest_plugins") - and self._configured - and not self._using_pyargs - ): - msg = ( - "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" - "It affects the entire test suite instead of just below the conftest as expected.\n" - " {}\n" - "Please move it to a top level conftest file at the rootdir:\n" - " {}\n" - "For more information, visit:\n" - " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" - ) - fail(msg.format(conftestpath, self._confcutdir), pytrace=False) - - # - # API for bootstrapping plugin loading - # - # - - def consider_preparse( - self, args: Sequence[str], *, exclude_only: bool = False - ) -> None: - """:meta private:""" - i = 0 - n = len(args) - while i < n: - opt = args[i] - i += 1 - if isinstance(opt, str): - if opt == "-p": - try: - parg = args[i] - except IndexError: - return - i += 1 - elif opt.startswith("-p"): - parg = opt[2:] - else: - continue - parg = parg.strip() - if exclude_only and not parg.startswith("no:"): - continue - self.consider_pluginarg(parg) - - def consider_pluginarg(self, arg: str) -> None: - """:meta private:""" - if arg.startswith("no:"): - name = arg[3:] - if name in essential_plugins: - raise UsageError(f"plugin {name} cannot be disabled") - - if name.endswith("conftest.py"): - raise UsageError( - f"Blocking conftest files using -p is not supported: -p no:{name}\n" - "conftest.py files are not plugins and cannot be disabled via -p.\n" - ) - - # PR #4304: remove stepwise if cacheprovider is blocked. - if name == "cacheprovider": - self.set_blocked("stepwise") - self.set_blocked("pytest_stepwise") - - self.set_blocked(name) - if not name.startswith("pytest_"): - self.set_blocked("pytest_" + name) - else: - name = arg - # Unblock the plugin. - self.unblock(name) - if not name.startswith("pytest_"): - self.unblock("pytest_" + name) - self.import_plugin(arg, consider_entry_points=True) - - def consider_conftest( - self, conftestmodule: types.ModuleType, registration_name: str - ) -> None: - """:meta private:""" - self.register(conftestmodule, name=registration_name) - - def consider_env(self) -> None: - """:meta private:""" - self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) - - def consider_module(self, mod: types.ModuleType) -> None: - """:meta private:""" - self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) - - def _import_plugin_specs( - self, spec: None | types.ModuleType | str | Sequence[str] - ) -> None: - plugins = _get_plugin_specs_as_list(spec) - for import_spec in plugins: - self.import_plugin(import_spec) - - def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: - """Import a plugin with ``modname``. - - If ``consider_entry_points`` is True, entry point names are also - considered to find a plugin. - """ - # Most often modname refers to builtin modules, e.g. "pytester", - # "terminal" or "capture". Those plugins are registered under their - # basename for historic purposes but must be imported with the - # _pytest prefix. - assert isinstance(modname, str), ( - f"module name as text required, got {modname!r}" - ) - if self.is_blocked(modname) or self.get_plugin(modname) is not None: - return - - importspec = "_pytest." + modname if modname in builtin_plugins else modname - self.rewrite_hook.mark_rewrite(importspec) - - if consider_entry_points: - loaded = self.load_setuptools_entrypoints("pytest11", name=modname) - if loaded: - return - - try: - if sys.version_info >= (3, 11): - mod = importlib.import_module(importspec) - else: - # On Python 3.10, import_module breaks - # testing/test_config.py::test_disable_plugin_autoload. - __import__(importspec) - mod = sys.modules[importspec] - except ImportError as e: - raise ImportError( - f'Error importing plugin "{modname}": {e.args[0]}' - ).with_traceback(e.__traceback__) from e - - except Skipped as e: - self.skipped_plugins.append((modname, e.msg or "")) - else: - self.register(mod, modname) - - -def _get_plugin_specs_as_list( - specs: None | types.ModuleType | str | Sequence[str], -) -> list[str]: - """Parse a plugins specification into a list of plugin names.""" - # None means empty. - if specs is None: - return [] - # Workaround for #3899 - a submodule which happens to be called "pytest_plugins". - if isinstance(specs, types.ModuleType): - return [] - # Comma-separated list. - if isinstance(specs, str): - return specs.split(",") if specs else [] - # Direct specification. - if isinstance(specs, collections.abc.Sequence): - return list(specs) - raise UsageError( - f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}" - ) - - -def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: - """Given an iterable of file names in a source distribution, return the "names" that should - be marked for assertion rewrite. - - For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in - the assertion rewrite mechanism. - - This function has to deal with dist-info based distributions and egg based distributions - (which are still very much in use for "editable" installs). - - Here are the file names as seen in a dist-info based distribution: - - pytest_mock/__init__.py - pytest_mock/_version.py - pytest_mock/plugin.py - pytest_mock.egg-info/PKG-INFO - - Here are the file names as seen in an egg based distribution: - - src/pytest_mock/__init__.py - src/pytest_mock/_version.py - src/pytest_mock/plugin.py - src/pytest_mock.egg-info/PKG-INFO - LICENSE - setup.py - - We have to take in account those two distribution flavors in order to determine which - names should be considered for assertion rewriting. - - More information: - https://github.com/pytest-dev/pytest-mock/issues/167 - """ - package_files = list(package_files) - seen_some = False - for fn in package_files: - is_simple_module = "/" not in fn and fn.endswith(".py") - is_package = fn.count("/") == 1 and fn.endswith("__init__.py") - if is_simple_module: - module_name, _ = os.path.splitext(fn) - # we ignore "setup.py" at the root of the distribution - # as well as editable installation finder modules made by setuptools - if module_name != "setup" and not module_name.startswith("__editable__"): - seen_some = True - yield module_name - elif is_package: - package_name = os.path.dirname(fn) - seen_some = True - yield package_name - - if not seen_some: - # At this point we did not find any packages or modules suitable for assertion - # rewriting, so we try again by stripping the first path component (to account for - # "src" based source trees for example). - # This approach lets us have the common case continue to be fast, as egg-distributions - # are rarer. - new_package_files = [] - for fn in package_files: - parts = fn.split("/") - new_fn = "/".join(parts[1:]) - if new_fn: - new_package_files.append(new_fn) - if new_package_files: - yield from _iter_rewritable_modules(new_package_files) - - -class _DeprecatedInicfgProxy(MutableMapping[str, Any]): - """Compatibility proxy for the deprecated Config.inicfg.""" - - __slots__ = ("_config",) - - def __init__(self, config: Config) -> None: - self._config = config - - def __getitem__(self, key: str) -> Any: - return self._config._inicfg[key].value - - def __setitem__(self, key: str, value: Any) -> None: - self._config._inicfg[key] = ConfigValue(value, origin="override", mode="toml") - - def __delitem__(self, key: str) -> None: - del self._config._inicfg[key] - - def __iter__(self) -> Iterator[str]: - return iter(self._config._inicfg) - - def __len__(self) -> int: - return len(self._config._inicfg) - - -@final -class Config: - """Access to configuration values, pluginmanager and plugin hooks. - - :param PytestPluginManager pluginmanager: - A pytest PluginManager. - - :param InvocationParams invocation_params: - Object containing parameters regarding the :func:`pytest.main` - invocation. - """ - - @final - @dataclasses.dataclass(frozen=True) - class InvocationParams: - """Holds parameters passed during :func:`pytest.main`. - - The object attributes are read-only. - - .. versionadded:: 5.1 - - .. note:: - - Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` - configuration option are handled by pytest, not being included in the ``args`` attribute. - - Plugins accessing ``InvocationParams`` must be aware of that. - """ - - args: tuple[str, ...] - """The command-line arguments as passed to :func:`pytest.main`.""" - plugins: Sequence[str | _PluggyPlugin] | None - """Extra plugins, might be `None`.""" - dir: pathlib.Path - """The directory from which :func:`pytest.main` was invoked.""" - - def __init__( - self, - *, - args: Iterable[str], - plugins: Sequence[str | _PluggyPlugin] | None, - dir: pathlib.Path, - ) -> None: - object.__setattr__(self, "args", tuple(args)) - object.__setattr__(self, "plugins", plugins) - object.__setattr__(self, "dir", dir) - - class ArgsSource(enum.Enum): - """Indicates the source of the test arguments. - - .. versionadded:: 7.2 - """ - - #: Command line arguments. - ARGS = enum.auto() - #: Invocation directory. - INVOCATION_DIR = enum.auto() - INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias - #: 'testpaths' configuration value. - TESTPATHS = enum.auto() - - # Set by cacheprovider plugin. - cache: Cache - - def __init__( - self, - pluginmanager: PytestPluginManager, - *, - invocation_params: InvocationParams | None = None, - prog: str | None = None, - ) -> None: - if invocation_params is None: - invocation_params = self.InvocationParams( - args=(), plugins=None, dir=pathlib.Path.cwd() - ) - - self.option = argparse.Namespace() - """Access to command line option as attributes. - - :type: argparse.Namespace - """ - - self.invocation_params = invocation_params - """The parameters with which pytest was invoked. - - :type: InvocationParams - """ - - self._parser = Parser( - usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]", - processopt=self._processopt, - prog=prog, - _ispytest=True, - ) - self.pluginmanager = pluginmanager - """The plugin manager handles plugin registration and hook invocation. - - :type: PytestPluginManager - """ - - self.stash = Stash() - """A place where plugins can store information on the config for their - own use. - - :type: Stash - """ - # Deprecated alias. Was never public. Can be removed in a few releases. - self._store = self.stash - - self.trace = self.pluginmanager.trace.root.get("config") - self.hook = self.pluginmanager.hook - self._inicache: dict[str, Any] = {} - self._inicfg: ConfigDict = {} - self._cleanup_stack = contextlib.ExitStack() - self.pluginmanager.register(self, "pytestconfig") - self._configured = False - self.hook.pytest_addoption.call_historic( - kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) - ) - self.args_source = Config.ArgsSource.ARGS - self.args: list[str] = [] - - if TYPE_CHECKING: - - @deprecated( - "config.inicfg is deprecated, use config.getini() to access configuration values instead.", - ) - @property - def inicfg(self) -> _DeprecatedInicfgProxy: - raise NotImplementedError() - else: - - @property - def inicfg(self) -> _DeprecatedInicfgProxy: - warnings.warn( - _pytest.deprecated.CONFIG_INICFG, - stacklevel=2, - ) - return _DeprecatedInicfgProxy(self) - - @property - def rootpath(self) -> pathlib.Path: - """The path to the :ref:`rootdir `. - - .. versionadded:: 6.1 - """ - return self._rootpath - - @property - def inipath(self) -> pathlib.Path | None: - """The path to the :ref:`configfile `. - - .. versionadded:: 6.1 - """ - return self._inipath - - def add_cleanup(self, func: Callable[[], None]) -> None: - """Add a function to be called when the config object gets out of - use (usually coinciding with pytest_unconfigure). - """ - self._cleanup_stack.callback(func) - - def _do_configure(self) -> None: - assert not self._configured - self._configured = True - self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) - - def _ensure_unconfigure(self) -> None: - try: - if self._configured: - self._configured = False - try: - self.hook.pytest_unconfigure(config=self) - finally: - self.hook.pytest_configure._call_history = [] - finally: - try: - self._cleanup_stack.close() - finally: - self._cleanup_stack = contextlib.ExitStack() - - def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( - "terminalreporter" - ) - assert terminalreporter is not None - return terminalreporter._tw - - def pytest_cmdline_parse( - self, pluginmanager: PytestPluginManager, args: list[str] - ) -> Config: - try: - self.parse(args) - except UsageError: - # Handle `--version --version` and `--help` here in a minimal fashion. - # This gets done via helpconfig normally, but its - # pytest_cmdline_main is not called in case of errors. - if getattr(self.option, "version", False) or "--version" in args: - from _pytest.helpconfig import show_version_verbose - - # Note that `--version` (single argument) is handled early by `Config.main()`, so the only - # way we are reaching this point is via `--version --version`. - show_version_verbose(self) - elif ( - getattr(self.option, "help", False) or "--help" in args or "-h" in args - ): - self._parser.optparser.print_help() - sys.stdout.write( - "\nNOTE: displaying only minimal help due to UsageError.\n\n" - ) - - raise - - return self - - def notify_exception( - self, - excinfo: ExceptionInfo[BaseException], - option: argparse.Namespace | None = None, - ) -> None: - if option and getattr(option, "fulltrace", False): - style: TracebackStyle = "long" - else: - style = "native" - excrepr = excinfo.getrepr( - funcargs=True, showlocals=getattr(option, "showlocals", False), style=style - ) - res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) - if not any(res): - for line in str(excrepr).split("\n"): - sys.stderr.write(f"INTERNALERROR> {line}\n") - sys.stderr.flush() - - def cwd_relative_nodeid(self, nodeid: str) -> str: - # nodeid's are relative to the rootpath, compute relative to cwd. - if self.invocation_params.dir != self.rootpath: - base_path_part, *nodeid_part = nodeid.split("::") - # Only process path part - fullpath = self.rootpath / base_path_part - relative_path = bestrelpath(self.invocation_params.dir, fullpath) - - nodeid = "::".join([relative_path, *nodeid_part]) - return nodeid - - @classmethod - def fromdictargs(cls, option_dict: Mapping[str, Any], args: list[str]) -> Config: - """Constructor usable for subprocesses.""" - config = get_config(args) - config.option.__dict__.update(option_dict) - config.parse(args, addopts=False) - for x in config.option.plugins: - config.pluginmanager.consider_pluginarg(x) - return config - - def _processopt(self, opt: Argument) -> None: - if not hasattr(self.option, opt.dest): - setattr(self.option, opt.dest, opt.default) - - @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config: Config) -> None: - # We haven't fully parsed the command line arguments yet, so - # early_config.args it not set yet. But we need it for - # discovering the initial conftests. So "pre-run" the logic here. - # It will be done for real in `parse()`. - args, _args_source = early_config._decide_args( - args=early_config.known_args_namespace.file_or_dir, - pyargs=early_config.known_args_namespace.pyargs, - testpaths=early_config.getini("testpaths"), - invocation_dir=early_config.invocation_params.dir, - rootpath=early_config.rootpath, - warn=False, - ) - self.pluginmanager._set_initial_conftests( - args=args, - pyargs=early_config.known_args_namespace.pyargs, - noconftest=early_config.known_args_namespace.noconftest, - rootpath=early_config.rootpath, - confcutdir=early_config.known_args_namespace.confcutdir, - invocation_dir=early_config.invocation_params.dir, - importmode=early_config.known_args_namespace.importmode, - consider_namespace_packages=early_config.getini( - "consider_namespace_packages" - ), - ) - - def _consider_importhook(self) -> None: - """Install the PEP 302 import hook if using assertion rewriting. - - Needs to parse the --assert= option from the commandline - and find all the installed plugins to mark them for rewriting - by the importhook. - """ - mode = getattr(self.known_args_namespace, "assertmode", "plain") - - disable_autoload = getattr( - self.known_args_namespace, "disable_plugin_autoload", False - ) or bool(os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")) - if mode == "rewrite": - import _pytest.assertion - - try: - hook = _pytest.assertion.install_importhook(self) - except SystemError: - mode = "plain" - else: - self._mark_plugins_for_rewrite(hook, disable_autoload) - self._warn_about_missing_assertion(mode) - - def _mark_plugins_for_rewrite( - self, hook: AssertionRewritingHook, disable_autoload: bool - ) -> None: - """Given an importhook, mark for rewrite any top-level - modules or packages in the distribution package for - all pytest plugins.""" - self.pluginmanager.rewrite_hook = hook - - if disable_autoload: - # We don't autoload from distribution package entry points, - # no need to continue. - return - - package_files = ( - str(file) - for dist in importlib.metadata.distributions() - if any(ep.group == "pytest11" for ep in dist.entry_points) - for file in dist.files or [] - ) - - for name in _iter_rewritable_modules(package_files): - hook.mark_rewrite(name) - - def _configure_python_path(self) -> None: - # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]` - for path in reversed(self.getini("pythonpath")): - sys.path.insert(0, str(path)) - self.add_cleanup(self._unconfigure_python_path) - - def _unconfigure_python_path(self) -> None: - for path in self.getini("pythonpath"): - path_str = str(path) - if path_str in sys.path: - sys.path.remove(path_str) - - def _validate_args(self, args: list[str], via: str) -> list[str]: - """Validate known args.""" - self._parser.extra_info["config source"] = via - try: - self._parser.parse_known_and_unknown_args( - args, namespace=copy.copy(self.option) - ) - finally: - self._parser.extra_info.pop("config source", None) - - return args - - def _decide_args( - self, - *, - args: list[str], - pyargs: bool, - testpaths: list[str], - invocation_dir: pathlib.Path, - rootpath: pathlib.Path, - warn: bool, - ) -> tuple[list[str], ArgsSource]: - """Decide the args (initial paths/nodeids) to use given the relevant inputs. - - :param warn: Whether can issue warnings. - - :returns: The args and the args source. Guaranteed to be non-empty. - """ - if args: - source = Config.ArgsSource.ARGS - result = args - else: - if invocation_dir == rootpath: - source = Config.ArgsSource.TESTPATHS - if pyargs: - result = testpaths - else: - result = [] - for path in testpaths: - result.extend(sorted(glob.iglob(path, recursive=True))) - if testpaths and not result: - if warn: - warning_text = ( - "No files were found in testpaths; " - "consider removing or adjusting your testpaths configuration. " - "Searching recursively from the current directory instead." - ) - self.issue_config_time_warning( - PytestConfigWarning(warning_text), stacklevel=3 - ) - else: - result = [] - if not result: - source = Config.ArgsSource.INVOCATION_DIR - result = [str(invocation_dir)] - return result, source - - @hookimpl(wrapper=True) - def pytest_collection(self) -> Generator[None, object, object]: - # Validate invalid configuration keys after collection is done so we - # take in account options added by late-loading conftest files. - try: - return (yield) - finally: - self._validate_config_options() - - def _checkversion(self) -> None: - import pytest - - minver = self.getini("minversion") - if minver: - # Imported lazily to improve start-up time. - from packaging.version import Version - - if Version(minver) > Version(pytest.__version__): - raise pytest.UsageError( - f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" - ) - - def _validate_config_options(self) -> None: - for key in sorted(self._get_unknown_ini_keys()): - self._warn_or_fail_if_strict(f"Unknown config option: {key}\n") - - def _validate_plugins(self) -> None: - required_plugins = sorted(self.getini("required_plugins")) - if not required_plugins: - return - - # Imported lazily to improve start-up time. - from packaging.requirements import InvalidRequirement - from packaging.requirements import Requirement - from packaging.version import Version - - plugin_info = self.pluginmanager.list_plugin_distinfo() - plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} - - missing_plugins = [] - for required_plugin in required_plugins: - try: - req = Requirement(required_plugin) - except InvalidRequirement: - missing_plugins.append(required_plugin) - continue - - if req.name not in plugin_dist_info: - missing_plugins.append(required_plugin) - elif not req.specifier.contains( - Version(plugin_dist_info[req.name]), prereleases=True - ): - missing_plugins.append(required_plugin) - - if missing_plugins: - raise UsageError( - "Missing required plugins: {}".format(", ".join(missing_plugins)), - ) - - def _warn_or_fail_if_strict(self, message: str) -> None: - strict_config = self.getini("strict_config") - if strict_config is None: - strict_config = self.getini("strict") - if strict_config: - raise UsageError(message) - - self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) - - def _get_unknown_ini_keys(self) -> set[str]: - known_keys = self._parser._inidict.keys() | self._parser._ini_aliases.keys() - return self._inicfg.keys() - known_keys - - def parse(self, args: list[str], addopts: bool = True) -> None: - # Parse given cmdline arguments into this config object. - assert self.args == [], ( - "can only parse cmdline args at most once per Config object" - ) - - self.hook.pytest_addhooks.call_historic( - kwargs=dict(pluginmanager=self.pluginmanager) - ) - - if addopts: - env_addopts = os.environ.get("PYTEST_ADDOPTS", "") - if len(env_addopts): - args[:] = ( - self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") - + args - ) - - # At this point, self.option contains only defaults from the _processopt - # callback. - ns = self._parser.parse_known_args(args, namespace=copy.copy(self.option)) - rootpath, inipath, inicfg, ignored_config_files = determine_setup( - inifile=ns.inifilename, - override_ini=ns.override_ini, - args=ns.file_or_dir, - rootdir_cmd_arg=ns.rootdir or None, - invocation_dir=self.invocation_params.dir, - ) - self._rootpath = rootpath - self._inipath = inipath - self._ignored_config_files = ignored_config_files - self._inicfg = inicfg - self._parser.extra_info["rootdir"] = str(self.rootpath) - self._parser.extra_info["inifile"] = str(self.inipath) - - self._parser.addini("addopts", "Extra command line options", "args") - self._parser.addini("minversion", "Minimally required pytest version") - self._parser.addini( - "pythonpath", type="paths", help="Add paths to sys.path", default=[] - ) - self._parser.addini( - "required_plugins", - "Plugins that must be present for pytest to run", - type="args", - default=[], - ) - - if addopts: - args[:] = ( - self._validate_args(self.getini("addopts"), "via addopts config") + args - ) - - self.known_args_namespace = self._parser.parse_known_args( - args, namespace=copy.copy(self.option) - ) - if addopts: - # addopts may have added overrides (especially via OverrideIniAction). - # The thing can be endlessly circular but we only do one level (#14442). - if overrides := parse_override_ini(self.known_args_namespace.override_ini): - self._inicfg.update(overrides) - self._inicache.clear() - self._checkversion() - self._consider_importhook() - self._configure_python_path() - self.pluginmanager.consider_preparse(args, exclude_only=False) - if ( - not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD") - and not self.known_args_namespace.disable_plugin_autoload - ): - # Autoloading from distribution package entry point has - # not been disabled. - self.pluginmanager.load_setuptools_entrypoints("pytest11") - # Otherwise only plugins explicitly specified in PYTEST_PLUGINS - # are going to be loaded. - self.pluginmanager.consider_env() - - # Parse again, now including options added in pytest_addoption - # by third-party plugins loaded above. This way they're available - # on early_config in the pytest_load_initial_conftests hook call below. - self.known_args_namespace = self._parser.parse_known_args( - args, namespace=copy.copy(self.option) - ) - - self._validate_plugins() - self._warn_about_skipped_plugins() - - if self.known_args_namespace.confcutdir is None: - if self.inipath is not None: - confcutdir = str(self.inipath.parent) - else: - confcutdir = str(self.rootpath) - self.known_args_namespace.confcutdir = confcutdir - try: - self.hook.pytest_load_initial_conftests( - early_config=self, args=args, parser=self._parser - ) - except ConftestImportFailure as e: - if self.known_args_namespace.help or self.known_args_namespace.version: - # we don't want to prevent --help/--version to work - # so just let it pass and print a warning at the end - self.issue_config_time_warning( - PytestConfigWarning(f"could not load initial conftests: {e.path}"), - stacklevel=2, - ) - else: - raise - - try: - self._parser.parse(args, namespace=self.option) - except PrintHelp: - return - - self.args, self.args_source = self._decide_args( - args=getattr(self.option, FILE_OR_DIR), - pyargs=self.option.pyargs, - testpaths=self.getini("testpaths"), - invocation_dir=self.invocation_params.dir, - rootpath=self.rootpath, - warn=True, - ) - - def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: - """Issue and handle a warning during the "configure" stage. - - During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` - function because it is not possible to have hook wrappers around ``pytest_configure``. - - This function is mainly intended for plugins that need to issue warnings during - ``pytest_configure`` (or similar stages). - - :param warning: The warning instance. - :param stacklevel: stacklevel forwarded to warnings.warn. - """ - if self.pluginmanager.is_blocked("warnings"): - return - - cmdline_filters = self.known_args_namespace.pythonwarnings or [] - config_filters = self.getini("filterwarnings") - - with warnings.catch_warnings(record=True) as records: - warnings.simplefilter("always", type(warning)) - apply_warning_filters(config_filters, cmdline_filters) - warnings.warn(warning, stacklevel=stacklevel) - - if records: - frame = sys._getframe(stacklevel - 1) - location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name - self.hook.pytest_warning_recorded.call_historic( - kwargs=dict( - warning_message=records[0], - when="config", - nodeid="", - location=location, - ) - ) - - def addinivalue_line(self, name: str, line: str) -> None: - """Add a line to a configuration option. The option must have been - declared but might not yet be set in which case the line becomes - the first line in its value.""" - x = self.getini(name) - assert isinstance(x, list) - x.append(line) # modifies the cached list inline - - def getini(self, name: str) -> Any: - """Return configuration value the an :ref:`configuration file `. - - If a configuration value is not defined in a - :ref:`configuration file `, then the ``default`` value - provided while registering the configuration through - :func:`parser.addini ` will be returned. - Please note that you can even provide ``None`` as a valid - default value. - - If ``default`` is not provided while registering using - :func:`parser.addini `, then a default value - based on the ``type`` parameter passed to - :func:`parser.addini ` will be returned. - The default values based on ``type`` are: - ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]`` - ``bool`` : ``False`` - ``string`` : empty string ``""`` - ``int`` : ``0`` - ``float`` : ``0.0`` - - If neither the ``default`` nor the ``type`` parameter is passed - while registering the configuration through - :func:`parser.addini `, then the configuration - is treated as a string and a default empty string '' is returned. - - If the specified name hasn't been registered through a prior - :func:`parser.addini ` call (usually from a - plugin), a ValueError is raised. - """ - canonical_name = self._parser._ini_aliases.get(name, name) - try: - return self._inicache[canonical_name] - except KeyError: - pass - self._inicache[canonical_name] = val = self._getini(canonical_name) - return val - - # Meant for easy monkeypatching by legacypath plugin. - # Can be inlined back (with no cover removed) once legacypath is gone. - def _getini_unknown_type(self, name: str, type: str, value: object): - msg = ( - f"Option {name} has unknown configuration type {type} with value {value!r}" - ) - raise ValueError(msg) # pragma: no cover - - def _getini(self, name: str): - # If this is an alias, resolve to canonical name. - canonical_name = self._parser._ini_aliases.get(name, name) - - try: - _description, type, default = self._parser._inidict[canonical_name] - except KeyError as e: - raise ValueError(f"unknown configuration value: {name!r}") from e - - # Collect all possible values (canonical name + aliases) from _inicfg. - # Each candidate is (ConfigValue, is_canonical). - candidates = [] - if canonical_name in self._inicfg: - candidates.append((self._inicfg[canonical_name], True)) - for alias, target in self._parser._ini_aliases.items(): - if target == canonical_name and alias in self._inicfg: - candidates.append((self._inicfg[alias], False)) - - if not candidates: - return default - - # Pick the best candidate based on precedence: - # 1. CLI override takes precedence over file, then - # 2. Canonical name takes precedence over alias. - selected = max(candidates, key=lambda x: (x[0].origin == "override", x[1]))[0] - value = selected.value - mode = selected.mode - - if mode == "ini": - # In ini mode, values are always str | list[str]. - assert isinstance(value, (str, list)) - return self._getini_ini(name, canonical_name, type, value, default) - elif mode == "toml": - return self._getini_toml(name, canonical_name, type, value, default) - else: - assert_never(mode) - - def _getini_ini( - self, - name: str, - canonical_name: str, - type: str, - value: str | list[str], - default: Any, - ): - """Handle config values read in INI mode. - - In INI mode, values are stored as str or list[str] only, and coerced - from string based on the registered type. - """ - # Note: some coercions are only required if we are reading from .ini - # files, because the file format doesn't contain type information, but - # when reading from toml (in ini mode) we will get either str or list of - # str values (see load_config_dict_from_file). For example: - # - # ini: - # a_line_list = "tests acceptance" - # - # in this case, we need to split the string to obtain a list of strings. - # - # toml (ini mode): - # a_line_list = ["tests", "acceptance"] - # - # in this case, we already have a list ready to use. - if type == "paths": - dp = ( - self.inipath.parent - if self.inipath is not None - else self.invocation_params.dir - ) - input_values = shlex.split(value) if isinstance(value, str) else value - return [dp / x for x in input_values] - elif type == "args": - return shlex.split(value) if isinstance(value, str) else value - elif type == "linelist": - if isinstance(value, str): - return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] - else: - return value - elif type == "bool": - return _strtobool(str(value).strip()) - elif type == "string": - return value - elif type == "int": - if not isinstance(value, str): - raise TypeError( - f"Expected an int string for option {name} of type integer, but got: {value!r}" - ) from None - return int(value) - elif type == "float": - if not isinstance(value, str): - raise TypeError( - f"Expected a float string for option {name} of type float, but got: {value!r}" - ) from None - return float(value) - else: - return self._getini_unknown_type(name, type, value) - - def _getini_toml( - self, - name: str, - canonical_name: str, - type: str, - value: object, - default: Any, - ): - """Handle TOML config values with strict type validation and no coercion. - - In TOML mode, values already have native types from TOML parsing. - We validate types match expectations exactly, including list items. - """ - value_type = builtins.type(value).__name__ - if type == "paths": - # Expect a list of strings. - if not isinstance(value, list): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list for type 'paths', " - f"got {value_type}: {value!r}" - ) - for i, item in enumerate(value): - if not isinstance(item, str): - item_type = builtins.type(item).__name__ - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list of strings, " - f"but item at index {i} is {item_type}: {item!r}" - ) - dp = ( - self.inipath.parent - if self.inipath is not None - else self.invocation_params.dir - ) - return [dp / x for x in value] - elif type in {"args", "linelist"}: - # Expect a list of strings. - if not isinstance(value, list): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list for type '{type}', " - f"got {value_type}: {value!r}" - ) - for i, item in enumerate(value): - if not isinstance(item, str): - item_type = builtins.type(item).__name__ - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list of strings, " - f"but item at index {i} is {item_type}: {item!r}" - ) - return list(value) - elif type == "bool": - # Expect a boolean. - if not isinstance(value, bool): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a bool, " - f"got {value_type}: {value!r}" - ) - return value - elif type == "int": - # Expect an integer (but not bool, which is a subclass of int). - if not isinstance(value, int) or isinstance(value, bool): - raise TypeError( - f"{self.inipath}: config option '{name}' expects an int, " - f"got {value_type}: {value!r}" - ) - return value - elif type == "float": - # Expect a float or integer only. - if not isinstance(value, (float, int)) or isinstance(value, bool): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a float, " - f"got {value_type}: {value!r}" - ) - return value - elif type == "string": - # Expect a string. - if not isinstance(value, str): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a string, " - f"got {value_type}: {value!r}" - ) - return value - else: - return self._getini_unknown_type(name, type, value) - - def _getconftest_pathlist( - self, name: str, path: pathlib.Path - ) -> list[pathlib.Path] | None: - try: - mod, relroots = self.pluginmanager._rget_with_confmod(name, path) - except KeyError: - return None - assert mod.__file__ is not None - modpath = pathlib.Path(mod.__file__).parent - values: list[pathlib.Path] = [] - for relroot in relroots: - if isinstance(relroot, os.PathLike): - relroot = pathlib.Path(relroot) - else: - relroot = relroot.replace("/", os.sep) - relroot = absolutepath(modpath / relroot) - values.append(relroot) - return values - - def getoption(self, name: str, default: Any = NOTSET, skip: bool = False): - """Return command line option value. - - :param name: Name of the option. You may also specify - the literal ``--OPT`` option instead of the "dest" option name. - :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`. - Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``. - :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. - Note that even if ``True``, if a default was specified it will be returned instead of a skip. - """ - name = self._parser._opt2dest.get(name, name) - try: - val = getattr(self.option, name) - if val is None and skip: - raise AttributeError(name) - return val - except AttributeError as e: - if default is not NOTSET: - return default - if skip: - import pytest - - pytest.skip(f"no {name!r} option found") - raise ValueError(f"no option named {name!r}") from e - - def getvalue(self, name: str, path=None): - """Deprecated, use getoption() instead.""" - return self.getoption(name) - - def getvalueorskip(self, name: str, path=None): - """Deprecated, use getoption(skip=True) instead.""" - return self.getoption(name, skip=True) - - #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`). - VERBOSITY_ASSERTIONS: Final = "assertions" - #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`). - VERBOSITY_TEST_CASES: Final = "test_cases" - #: Verbosity type for failed subtests (see :confval:`verbosity_subtests`). - VERBOSITY_SUBTESTS: Final = "subtests" - - _VERBOSITY_INI_DEFAULT: Final = "auto" - - def get_verbosity(self, verbosity_type: str | None = None) -> int: - r"""Retrieve the verbosity level for a fine-grained verbosity type. - - :param verbosity_type: Verbosity type to get level for. If a level is - configured for the given type, that value will be returned. If the - given type is not a known verbosity type, the global verbosity - level will be returned. If the given type is None (default), the - global verbosity level will be returned. - - To configure a level for a fine-grained verbosity type, the - configuration file should have a setting for the configuration name - and a numeric value for the verbosity level. A special value of "auto" - can be used to explicitly use the global verbosity level. - - Example: - - .. tab:: toml - - .. code-block:: toml - - [tool.pytest] - verbosity_assertions = 2 - - .. tab:: ini - - .. code-block:: ini - - [pytest] - verbosity_assertions = 2 - - .. code-block:: console - - pytest -v - - .. code-block:: python - - print(config.get_verbosity()) # 1 - print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2 - """ - global_level = self.getoption("verbose", default=0) - assert isinstance(global_level, int) - if verbosity_type is None: - return global_level - - ini_name = Config._verbosity_ini_name(verbosity_type) - if ini_name not in self._parser._inidict: - return global_level - - level = self.getini(ini_name) - if level == Config._VERBOSITY_INI_DEFAULT: - return global_level - - return int(level) - - @staticmethod - def _verbosity_ini_name(verbosity_type: str) -> str: - return f"verbosity_{verbosity_type}" - - @staticmethod - def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: - """Add a output verbosity configuration option for the given output type. - - :param parser: Parser for command line arguments and config-file values. - :param verbosity_type: Fine-grained verbosity category. - :param help: Description of the output this type controls. - - The value should be retrieved via a call to - :py:func:`config.get_verbosity(type) `. - """ - parser.addini( - Config._verbosity_ini_name(verbosity_type), - help=help, - type="string", - default=Config._VERBOSITY_INI_DEFAULT, - ) - - def _warn_about_missing_assertion(self, mode: str) -> None: - if not _assertion_supported(): - if mode == "plain": - warning_text = ( - "ASSERTIONS ARE NOT EXECUTED" - " and FAILING TESTS WILL PASS. Are you" - " using python -O?" - ) - else: - warning_text = ( - "assertions not in test modules or" - " plugins will be ignored" - " because assert statements are not executed " - "by the underlying Python interpreter " - "(are you using python -O?)\n" - ) - self.issue_config_time_warning( - PytestConfigWarning(warning_text), - stacklevel=3, - ) - - def _warn_about_skipped_plugins(self) -> None: - for module_name, msg in self.pluginmanager.skipped_plugins: - self.issue_config_time_warning( - PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"), - stacklevel=2, - ) - - -def _assertion_supported() -> bool: - try: - assert False - except AssertionError: - return True - else: - return False # type: ignore[unreachable] - - -def create_terminal_writer( - config: Config, file: TextIO | None = None -) -> TerminalWriter: - """Create a TerminalWriter instance configured according to the options - in the config object. - - Every code which requires a TerminalWriter object and has access to a - config object should use this function. - """ - tw = TerminalWriter(file=file) - - if config.option.color == "yes": - tw.hasmarkup = True - elif config.option.color == "no": - tw.hasmarkup = False - - if config.option.code_highlight == "yes": - tw.code_highlight = True - elif config.option.code_highlight == "no": - tw.code_highlight = False - - return tw - - -def _strtobool(val: str) -> bool: - """Convert a string representation of truth to True or False. - - True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values - are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if - 'val' is anything else. - - .. note:: Copied from distutils.util. - """ - val = val.lower() - if val in ("y", "yes", "t", "true", "on", "1"): - return True - elif val in ("n", "no", "f", "false", "off", "0"): - return False - else: - raise ValueError(f"invalid truth value {val!r}") - - -@lru_cache(maxsize=50) -def parse_warning_filter( - arg: str, *, escape: bool -) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: - """Parse a warnings filter string. - - This is copied from warnings._setoption with the following changes: - - * Does not apply the filter. - * Escaping is optional. - * Raises UsageError so we get nice error messages on failure. - """ - __tracebackhide__ = True - error_template = dedent( - f"""\ - while parsing the following warning configuration: - - {arg} - - This error occurred: - - {{error}} - """ - ) - - parts = arg.split(":") - if len(parts) > 5: - doc_url = ( - "https://docs.python.org/3/library/warnings.html#describing-warning-filters" - ) - error = dedent( - f"""\ - Too many fields ({len(parts)}), expected at most 5 separated by colons: - - action:message:category:module:line - - For more information please consult: {doc_url} - """ - ) - raise UsageError(error_template.format(error=error)) - - while len(parts) < 5: - parts.append("") - action_, message, category_, module, lineno_ = (s.strip() for s in parts) - try: - action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] - except warnings._OptionError as e: - raise UsageError(error_template.format(error=str(e))) from None - try: - category: type[Warning] = _resolve_warning_category(category_) - except ImportError: - raise - except Exception: - exc_info = ExceptionInfo.from_current() - exception_text = exc_info.getrepr(style="native") - raise UsageError(error_template.format(error=exception_text)) from None - if message and escape: - message = re.escape(message) - if module and escape: - module = re.escape(module) + r"\Z" - if lineno_: - try: - lineno = int(lineno_) - if lineno < 0: - raise ValueError("number is negative") - except ValueError as e: - raise UsageError( - error_template.format(error=f"invalid lineno {lineno_!r}: {e}") - ) from None - else: - lineno = 0 - try: - re.compile(message) - re.compile(module) - except re.error as e: - raise UsageError( - error_template.format(error=f"Invalid regex {e.pattern!r}: {e}") - ) from None - return action, message, category, module, lineno - - -def _resolve_warning_category(category: str) -> type[Warning]: - """ - Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) - propagate so we can get access to their tracebacks (#9218). - """ - __tracebackhide__ = True - if not category: - return Warning - - if "." not in category: - import builtins as m - - klass = category - else: - module, _, klass = category.rpartition(".") - m = importlib.import_module(module) - cat = getattr(m, klass) - if not issubclass(cat, Warning): - raise UsageError(f"{cat} is not a Warning subclass") - return cast(type[Warning], cat) - - -def apply_warning_filters( - config_filters: Iterable[str], cmdline_filters: Iterable[str] -) -> None: - """Applies pytest-configured filters to the warnings module""" - # Filters should have this precedence: cmdline options, config. - # Filters should be applied in the inverse order of precedence. - for arg in config_filters: - try: - warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) - except ImportError as e: - warnings.warn( - f"Failed to import filter module '{e.name}': {arg}", PytestConfigWarning - ) - continue - - for arg in cmdline_filters: - try: - warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) - except ImportError as e: - warnings.warn( - f"Failed to import filter module '{e.name}': {arg}", PytestConfigWarning - ) - continue diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index cafbe69..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/argparsing.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/argparsing.cpython-311.pyc deleted file mode 100644 index 9d16586..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/argparsing.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/exceptions.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/exceptions.cpython-311.pyc deleted file mode 100644 index 6e8d370..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/exceptions.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/findpaths.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/findpaths.cpython-311.pyc deleted file mode 100644 index bc9dc23..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/config/__pycache__/findpaths.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/argparsing.py b/tests/venv2/lib/python3.11/site-packages/_pytest/config/argparsing.py deleted file mode 100644 index f70e276..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/config/argparsing.py +++ /dev/null @@ -1,503 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import argparse -from collections.abc import Callable -from collections.abc import Sequence -import os -import sys -import textwrap -from typing import Any -from typing import final -from typing import Literal -from typing import NoReturn - -from .exceptions import UsageError -import _pytest._io -from _pytest.compat import NOTSET -from _pytest.deprecated import check_ispytest - - -FILE_OR_DIR = "file_or_dir" - - -@final -class Parser: - """Parser for command line arguments and config-file values. - - :ivar extra_info: Dict of generic param -> value to display in case - there's an error processing the command line arguments. - """ - - def __init__( - self, - usage: str | None = None, - processopt: Callable[[Argument], None] | None = None, - *, - prog: str | None = None, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - - from _pytest._argcomplete import filescompleter - - self._processopt = processopt - self.extra_info: dict[str, Any] = {} - self.optparser = PytestArgumentParser(usage, self.extra_info, prog=prog) - anonymous_arggroup = self.optparser.add_argument_group("Custom options") - self._anonymous = OptionGroup( - anonymous_arggroup, "_anonymous", self, _ispytest=True - ) - self._groups = [self._anonymous] - # Maps option strings -> dest, e.g. "-V" and "--version" to "version". - self._opt2dest: dict[str, str] = {} - file_or_dir_arg = self.optparser.add_argument(FILE_OR_DIR, nargs="*") - file_or_dir_arg.completer = filescompleter # type: ignore - - self._inidict: dict[str, tuple[str, str, Any]] = {} - # Maps alias -> canonical name. - self._ini_aliases: dict[str, str] = {} - - @property - def prog(self) -> str: - return self.optparser.prog - - @prog.setter - def prog(self, value: str) -> None: - self.optparser.prog = value - - def processoption(self, option: Argument) -> None: - if self._processopt: - if option.dest: - self._processopt(option) - - def getgroup( - self, name: str, description: str = "", after: str | None = None - ) -> OptionGroup: - """Get (or create) a named option Group. - - :param name: Name of the option group. - :param description: Long description for --help output. - :param after: Name of another group, used for ordering --help output. - :returns: The option group. - - The returned group object has an ``addoption`` method with the same - signature as :func:`parser.addoption ` but - will be shown in the respective group in the output of - ``pytest --help``. - """ - for group in self._groups: - if group.name == name: - return group - - arggroup = self.optparser.add_argument_group(description or name) - group = OptionGroup(arggroup, name, self, _ispytest=True) - i = 0 - for i, grp in enumerate(self._groups): - if grp.name == after: - break - self._groups.insert(i + 1, group) - # argparse doesn't provide a way to control `--help` order, so must - # access its internals ☹. - self.optparser._action_groups.insert(i + 1, self.optparser._action_groups.pop()) - return group - - def addoption(self, *opts: str, **attrs: Any) -> None: - """Register a command line option. - - :param opts: - Option names, can be short or long options. - :param attrs: - Same attributes as the argparse library's :meth:`add_argument() - ` function accepts. - - After command line parsing, options are available on the pytest config - object via ``config.option.NAME`` where ``NAME`` is usually set - by passing a ``dest`` attribute, for example - ``addoption("--long", dest="NAME", ...)``. - """ - self._anonymous.addoption(*opts, **attrs) - - def parse( - self, - args: Sequence[str | os.PathLike[str]], - namespace: argparse.Namespace | None = None, - ) -> argparse.Namespace: - """Parse the arguments. - - Unlike ``parse_known_args`` and ``parse_known_and_unknown_args``, - raises PrintHelp on `--help` and UsageError on unknown flags - - :meta private: - """ - from _pytest._argcomplete import try_argcomplete - - try_argcomplete(self.optparser) - strargs = [os.fspath(x) for x in args] - if namespace is None: - namespace = argparse.Namespace() - try: - namespace._raise_print_help = True - return self.optparser.parse_intermixed_args(strargs, namespace=namespace) - finally: - del namespace._raise_print_help - - def parse_known_args( - self, - args: Sequence[str | os.PathLike[str]], - namespace: argparse.Namespace | None = None, - ) -> argparse.Namespace: - """Parse the known arguments at this point. - - :returns: An argparse namespace object. - """ - return self.parse_known_and_unknown_args(args, namespace=namespace)[0] - - def parse_known_and_unknown_args( - self, - args: Sequence[str | os.PathLike[str]], - namespace: argparse.Namespace | None = None, - ) -> tuple[argparse.Namespace, list[str]]: - """Parse the known arguments at this point, and also return the - remaining unknown flag arguments. - - :returns: - A tuple containing an argparse namespace object for the known - arguments, and a list of unknown flag arguments. - """ - strargs = [os.fspath(x) for x in args] - if sys.version_info < (3, 12, 8) or (3, 13) <= sys.version_info < (3, 13, 1): - # Older argparse have a bugged parse_known_intermixed_args. - namespace, unknown = self.optparser.parse_known_args(strargs, namespace) - assert namespace is not None - file_or_dir = getattr(namespace, FILE_OR_DIR) - unknown_flags: list[str] = [] - for arg in unknown: - (unknown_flags if arg.startswith("-") else file_or_dir).append(arg) - return namespace, unknown_flags - else: - return self.optparser.parse_known_intermixed_args(strargs, namespace) - - def addini( - self, - name: str, - help: str, - type: Literal[ - "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" - ] - | None = None, - default: Any = NOTSET, - *, - aliases: Sequence[str] = (), - ) -> None: - """Register a configuration file option. - - :param name: - Name of the configuration. - :param type: - Type of the configuration. Can be: - - * ``string``: a string - * ``bool``: a boolean - * ``args``: a list of strings, separated as in a shell - * ``linelist``: a list of strings, separated by line breaks - * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell - * ``pathlist``: a list of ``py.path``, separated as in a shell - * ``int``: an integer - * ``float``: a floating-point number - - .. versionadded:: 8.4 - - The ``float`` and ``int`` types. - - For ``paths`` and ``pathlist`` types, they are considered relative to the config-file. - In case the execution is happening without a config-file defined, - they will be considered relative to the current working directory (for example with ``--override-ini``). - - .. versionadded:: 7.0 - The ``paths`` variable type. - - .. versionadded:: 8.1 - Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of a config-file. - - Defaults to ``string`` if ``None`` or not passed. - :param default: - Default value if no config-file option exists but is queried. - :param aliases: - Additional names by which this option can be referenced. - Aliases resolve to the canonical name. - - .. versionadded:: 9.0 - The ``aliases`` parameter. - - The value of configuration keys can be retrieved via a call to - :py:func:`config.getini(name) `. - """ - assert type in ( - None, - "string", - "paths", - "pathlist", - "args", - "linelist", - "bool", - "int", - "float", - ) - if type is None: - type = "string" - if default is NOTSET: - default = get_ini_default_for_type(type) - - self._inidict[name] = (help, type, default) - - for alias in aliases: - if alias in self._inidict: - raise ValueError( - f"alias {alias!r} conflicts with existing configuration option" - ) - if (already := self._ini_aliases.get(alias)) is not None: - raise ValueError(f"{alias!r} is already an alias of {already!r}") - self._ini_aliases[alias] = name - - -def get_ini_default_for_type( - type: Literal[ - "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" - ], -) -> Any: - """ - Used by addini to get the default value for a given config option type, when - default is not supplied. - """ - if type in ("paths", "pathlist", "args", "linelist"): - return [] - elif type == "bool": - return False - elif type == "int": - return 0 - elif type == "float": - return 0.0 - else: - return "" - - -class Argument: - """An option defined in an OptionGroup.""" - - def __init__(self, action: argparse.Action) -> None: - self._action = action - - def attrs(self) -> dict[str, Any]: - return self._action.__dict__ - - def names(self) -> Sequence[str]: - return self._action.option_strings - - @property - def dest(self) -> str: - return self._action.dest - - @property - def default(self) -> Any: - return self._action.default - - @property - def type(self) -> Any | None: - return self._action.type - - def __repr__(self) -> str: - action = getattr(self, "_action", None) - if action is None: - return "Argument()" - args: list[str] = [] - args += ["opts: " + repr(self.names())] - args += ["dest: " + repr(self.dest)] - if action.type: - args += ["type: " + repr(self.type)] - args += ["default: " + repr(self.default)] - return "Argument({})".format(", ".join(args)) - - -class OptionGroup: - """A group of options shown in its own section.""" - - def __init__( - self, - arggroup: argparse._ArgumentGroup, - name: str, - parser: Parser | None, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self._arggroup = arggroup - self.name = name - self.options: list[Argument] = [] - self.parser = parser - - def addoption(self, *opts: str, **attrs: Any) -> None: - """Add an option to this group. - - If a shortened version of a long option is specified, it will - be suppressed in the help. ``addoption('--twowords', '--two-words')`` - results in help showing ``--two-words`` only, but ``--twowords`` gets - accepted **and** the automatic destination is in ``args.twowords``. - - :param opts: - Option names, can be short or long options. - Note that lower-case short options (e.g. `-x`) are reserved. - :param attrs: - Same attributes as the argparse library's :meth:`add_argument() - ` function accepts. - """ - conflict = set(opts).intersection( - name for opt in self.options for name in opt.names() - ) - if conflict: - raise ValueError(f"option names {conflict} already added") - self._addoption_inner(opts, attrs, allow_reserved=False) - - def _addoption(self, *opts: str, **attrs: Any) -> None: - """Like addoption(), but also allows registering short lower case options (e.g. -x), - which are reserved for pytest core.""" - self._addoption_inner(opts, attrs, allow_reserved=True) - - def _addoption_inner( - self, opts: tuple[str, ...], attrs: dict[str, Any], allow_reserved: bool - ) -> None: - if not allow_reserved: - for opt in opts: - if len(opt) >= 2 and opt[0] == "-" and opt[1].islower(): - raise ValueError("lowercase short options are reserved") - - action = self._arggroup.add_argument(*opts, **attrs) - option = Argument(action) - self.options.append(option) - if self.parser: - for name in option.names(): - self.parser._opt2dest[name] = option.dest - self.parser.processoption(option) - - -class PytestArgumentParser(argparse.ArgumentParser): - def __init__( - self, - usage: str | None, - extra_info: dict[str, str], - *, - prog: str | None = None, - ) -> None: - super().__init__( - usage=usage, - prog=prog, - add_help=False, - formatter_class=DropShorterLongHelpFormatter, - allow_abbrev=False, - fromfile_prefix_chars="@", - ) - # extra_info is a dict of (param -> value) to display if there's - # an usage error to provide more contextual information to the user. - self.extra_info = extra_info - - def error(self, message: str) -> NoReturn: - """Transform argparse error message into UsageError.""" - # TODO(py313): Replace with `exit_on_error=False`. Note that while it - # was added in Python 3.9, it was broken until 3.13 (cpython#121018). - msg = f"{self.prog}: error: {message}" - if self.extra_info: - msg += "\n" + "\n".join( - f" {k}: {v}" for k, v in sorted(self.extra_info.items()) - ) - raise UsageError(self.format_usage() + msg) - - -class DropShorterLongHelpFormatter(argparse.HelpFormatter): - """Shorten help for long options that differ only in extra hyphens. - - - Collapse **long** options that are the same except for extra hyphens. - - Shortcut if there are only two options and one of them is a short one. - """ - - def __init__(self, *args: Any, **kwargs: Any) -> None: - # Use more accurate terminal width. - if "width" not in kwargs: - kwargs["width"] = _pytest._io.get_terminal_width() - super().__init__(*args, **kwargs) - - def _format_action_invocation(self, action: argparse.Action) -> str: - orgstr = super()._format_action_invocation(action) - if orgstr and orgstr[0] != "-": # only optional arguments - return orgstr - options = orgstr.split(", ") - if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2): - # a shortcut for '-h, --help' or '--abc', '-a' - return orgstr - return_list = [] - short_long: dict[str, str] = {} - for option in options: - if len(option) == 2 or option[2] == " ": - continue - assert option.startswith("--"), ( - f'long optional argument without "--": [{option}]' - ) - xxoption = option[2:] - shortened = xxoption.replace("-", "") - if shortened not in short_long or len(short_long[shortened]) < len( - xxoption - ): - short_long[shortened] = xxoption - # now short_long has been filled out to the longest with dashes - # **and** we keep the right option ordering from add_argument - for option in options: - if len(option) == 2 or option[2] == " ": - return_list.append(option) - if option[2:] == short_long.get(option.replace("-", "")): - return_list.append(option.replace(" ", "=", 1)) - return ", ".join(return_list) - - def _split_lines(self, text: str, width: int) -> list[str]: - """Wrap lines after splitting on original newlines. - - This allows to have explicit line breaks in the help text. - """ - lines = [] - for line in text.splitlines(): - lines.extend(textwrap.wrap(line.strip(), width)) - return lines - - -class OverrideIniAction(argparse.Action): - """Custom argparse action that makes a CLI flag equivalent to overriding an - option, in addition to behaving like `store_true`. - - This can simplify things since code only needs to inspect the config option - and not consider the CLI flag. - """ - - def __init__( - self, - option_strings: Sequence[str], - dest: str, - nargs: int | str | None = None, - *args, - ini_option: str, - ini_value: str, - **kwargs, - ) -> None: - super().__init__(option_strings, dest, 0, *args, **kwargs) - self.ini_option = ini_option - self.ini_value = ini_value - - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - *args, - **kwargs, - ) -> None: - setattr(namespace, self.dest, True) - current_overrides = getattr(namespace, "override_ini", None) - if current_overrides is None: - current_overrides = [] - current_overrides.append(f"{self.ini_option}={self.ini_value}") - setattr(namespace, "override_ini", current_overrides) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/exceptions.py b/tests/venv2/lib/python3.11/site-packages/_pytest/config/exceptions.py deleted file mode 100644 index d84a9ea..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/config/exceptions.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations - -from typing import final - - -@final -class UsageError(Exception): - """Error in pytest usage or invocation.""" - - __module__ = "pytest" - - -class PrintHelp(Exception): - """Raised when pytest should print its help to skip the rest of the - argument parsing and validation.""" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/config/findpaths.py b/tests/venv2/lib/python3.11/site-packages/_pytest/config/findpaths.py deleted file mode 100644 index e74546c..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/config/findpaths.py +++ /dev/null @@ -1,350 +0,0 @@ -from __future__ import annotations - -from collections.abc import Iterable -from collections.abc import Sequence -from dataclasses import dataclass -from dataclasses import KW_ONLY -import os -from pathlib import Path -import sys -from typing import Literal -from typing import TypeAlias - -import iniconfig - -from .exceptions import UsageError -from _pytest.outcomes import fail -from _pytest.pathlib import absolutepath -from _pytest.pathlib import commonpath -from _pytest.pathlib import safe_exists - - -@dataclass(frozen=True) -class ConfigValue: - """Represents a configuration value with its origin and parsing mode. - - This allows tracking whether a value came from a configuration file - or from a CLI override (--override-ini), which is important for - determining precedence when dealing with ini option aliases. - - The mode tracks the parsing mode/data model used for the value: - - "ini": from INI files or [tool.pytest.ini_options], where the only - supported value types are `str` or `list[str]`. - - "toml": from TOML files (not in INI mode), where native TOML types - are preserved. - """ - - value: object - _: KW_ONLY - origin: Literal["file", "override"] - mode: Literal["ini", "toml"] - - -ConfigDict: TypeAlias = dict[str, ConfigValue] - - -def _parse_ini_config(path: Path) -> iniconfig.IniConfig: - """Parse the given generic '.ini' file using legacy IniConfig parser, returning - the parsed object. - - Raise UsageError if the file cannot be parsed. - """ - try: - return iniconfig.IniConfig(str(path)) - except iniconfig.ParseError as exc: - raise UsageError(str(exc)) from exc - - -def load_config_dict_from_file( - filepath: Path, -) -> ConfigDict | None: - """Load pytest configuration from the given file path, if supported. - - Return None if the file does not contain valid pytest configuration. - """ - # Configuration from ini files are obtained from the [pytest] section, if present. - if filepath.suffix == ".ini": - iniconfig = _parse_ini_config(filepath) - - if "pytest" in iniconfig: - return { - k: ConfigValue(v, origin="file", mode="ini") - for k, v in iniconfig["pytest"].items() - } - else: - # "pytest.ini" files are always the source of configuration, even if empty. - if filepath.name in {"pytest.ini", ".pytest.ini"}: - return {} - - # '.cfg' files are considered if they contain a "[tool:pytest]" section. - elif filepath.suffix == ".cfg": - iniconfig = _parse_ini_config(filepath) - - if "tool:pytest" in iniconfig.sections: - return { - k: ConfigValue(v, origin="file", mode="ini") - for k, v in iniconfig["tool:pytest"].items() - } - elif "pytest" in iniconfig.sections: - # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that - # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086). - fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False) - - # '.toml' files are considered if they contain a [tool.pytest] table (toml mode) - # or [tool.pytest.ini_options] table (ini mode) for pyproject.toml, - # or [pytest] table (toml mode) for pytest.toml/.pytest.toml. - elif filepath.suffix == ".toml": - if sys.version_info >= (3, 11): - import tomllib - else: - import tomli as tomllib - - toml_text = filepath.read_text(encoding="utf-8") - try: - config = tomllib.loads(toml_text) - except tomllib.TOMLDecodeError as exc: - raise UsageError(f"{filepath}: {exc}") from exc - - # pytest.toml and .pytest.toml use [pytest] table directly. - if filepath.name in ("pytest.toml", ".pytest.toml"): - pytest_config = config.get("pytest", {}) - if pytest_config: - # TOML mode - preserve native TOML types. - return { - k: ConfigValue(v, origin="file", mode="toml") - for k, v in pytest_config.items() - } - # "pytest.toml" files are always the source of configuration, even if empty. - return {} - - # pyproject.toml uses [tool.pytest] or [tool.pytest.ini_options]. - else: - tool_pytest = config.get("tool", {}).get("pytest", {}) - - # Check for toml mode config: [tool.pytest] with content outside of ini_options. - toml_config = {k: v for k, v in tool_pytest.items() if k != "ini_options"} - # Check for ini mode config: [tool.pytest.ini_options]. - ini_config = tool_pytest.get("ini_options", None) - - if toml_config and ini_config: - raise UsageError( - f"{filepath}: Cannot use both [tool.pytest] (native TOML types) and " - "[tool.pytest.ini_options] (string-based INI format) simultaneously. " - "Please use [tool.pytest] with native TOML types (recommended) " - "or [tool.pytest.ini_options] for backwards compatibility." - ) - - if toml_config: - # TOML mode - preserve native TOML types. - return { - k: ConfigValue(v, origin="file", mode="toml") - for k, v in toml_config.items() - } - - elif ini_config is not None: - # INI mode - TOML supports richer data types than INI files, but we need to - # convert all scalar values to str for compatibility with the INI system. - def make_scalar(v: object) -> str | list[str]: - return v if isinstance(v, list) else str(v) - - return { - k: ConfigValue(make_scalar(v), origin="file", mode="ini") - for k, v in ini_config.items() - } - - return None - - -def locate_config( - invocation_dir: Path, - args: Iterable[Path], -) -> tuple[Path | None, Path | None, ConfigDict, Sequence[str]]: - """Search in the list of arguments for a valid ini-file for pytest, - and return a tuple of (rootdir, inifile, cfg-dict, ignored-config-files), where - ignored-config-files is a list of config basenames found that contain - pytest configuration but were ignored.""" - config_names = [ - "pytest.toml", - ".pytest.toml", - "pytest.ini", - ".pytest.ini", - "pyproject.toml", - "tox.ini", - "setup.cfg", - ] - args = [x for x in args if not str(x).startswith("-")] - if not args: - args = [invocation_dir] - found_pyproject_toml: Path | None = None - ignored_config_files: list[str] = [] - - for arg in args: - argpath = absolutepath(arg) - for base in (argpath, *argpath.parents): - for config_name in config_names: - p = base / config_name - if p.is_file(): - if p.name == "pyproject.toml" and found_pyproject_toml is None: - found_pyproject_toml = p - ini_config = load_config_dict_from_file(p) - if ini_config is not None: - index = config_names.index(config_name) - for remainder in config_names[index + 1 :]: - p2 = base / remainder - if ( - p2.is_file() - and load_config_dict_from_file(p2) is not None - ): - ignored_config_files.append(remainder) - return base, p, ini_config, ignored_config_files - if found_pyproject_toml is not None: - return found_pyproject_toml.parent, found_pyproject_toml, {}, [] - return None, None, {}, [] - - -def get_common_ancestor( - invocation_dir: Path, - paths: Iterable[Path], -) -> Path: - common_ancestor: Path | None = None - for path in paths: - if not path.exists(): - continue - if common_ancestor is None: - common_ancestor = path - else: - if common_ancestor in path.parents or path == common_ancestor: - continue - elif path in common_ancestor.parents: - common_ancestor = path - else: - shared = commonpath(path, common_ancestor) - if shared is not None: - common_ancestor = shared - if common_ancestor is None: - common_ancestor = invocation_dir - elif common_ancestor.is_file(): - common_ancestor = common_ancestor.parent - return common_ancestor - - -def get_dirs_from_args(args: Iterable[str]) -> list[Path]: - def is_option(x: str) -> bool: - return x.startswith("-") - - def get_file_part_from_node_id(x: str) -> str: - return x.split("::", maxsplit=1)[0] - - def get_dir_from_path(path: Path) -> Path: - if path.is_dir(): - return path - return path.parent - - # These look like paths but may not exist - possible_paths = ( - absolutepath(get_file_part_from_node_id(arg)) - for arg in args - if not is_option(arg) - ) - - return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)] - - -def parse_override_ini(override_ini: Sequence[str] | None) -> ConfigDict: - """Parse the -o/--override-ini command line arguments and return the overrides. - - :raises UsageError: - If one of the values is malformed. - """ - overrides = {} - # override_ini is a list of "ini=value" options. - # Always use the last item if multiple values are set for same ini-name, - # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. - for ini_config in override_ini or (): - try: - key, user_ini_value = ini_config.split("=", 1) - except ValueError as e: - raise UsageError( - f"-o/--override-ini expects option=value style (got: {ini_config!r})." - ) from e - else: - overrides[key] = ConfigValue(user_ini_value, origin="override", mode="ini") - return overrides - - -CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." - - -def determine_setup( - *, - inifile: str | None, - override_ini: Sequence[str] | None, - args: Sequence[str], - rootdir_cmd_arg: str | None, - invocation_dir: Path, -) -> tuple[Path, Path | None, ConfigDict, Sequence[str]]: - """Determine the rootdir, inifile and ini configuration values from the - command line arguments. - - :param inifile: - The `--inifile` command line argument, if given. - :param override_ini: - The -o/--override-ini command line arguments, if given. - :param args: - The free command line arguments. - :param rootdir_cmd_arg: - The `--rootdir` command line argument, if given. - :param invocation_dir: - The working directory when pytest was invoked. - - :raises UsageError: - """ - rootdir = None - dirs = get_dirs_from_args(args) - ignored_config_files: Sequence[str] = [] - - if inifile: - inipath_ = absolutepath(inifile) - inipath: Path | None = inipath_ - inicfg = load_config_dict_from_file(inipath_) or {} - if rootdir_cmd_arg is None: - rootdir = inipath_.parent - else: - ancestor = get_common_ancestor(invocation_dir, dirs) - rootdir, inipath, inicfg, ignored_config_files = locate_config( - invocation_dir, [ancestor] - ) - if rootdir is None and rootdir_cmd_arg is None: - for possible_rootdir in (ancestor, *ancestor.parents): - if (possible_rootdir / "setup.py").is_file(): - rootdir = possible_rootdir - break - else: - if dirs != [ancestor]: - rootdir, inipath, inicfg, _ = locate_config(invocation_dir, dirs) - if rootdir is None: - rootdir = get_common_ancestor( - invocation_dir, [invocation_dir, ancestor] - ) - if is_fs_root(rootdir): - rootdir = ancestor - if rootdir_cmd_arg: - rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) - if not rootdir.is_dir(): - raise UsageError( - f"Directory '{rootdir}' not found. Check your '--rootdir' option." - ) - - ini_overrides = parse_override_ini(override_ini) - inicfg.update(ini_overrides) - - assert rootdir is not None - return rootdir, inipath, inicfg, ignored_config_files - - -def is_fs_root(p: Path) -> bool: - r""" - Return True if the given path is pointing to the root of the - file system ("/" on Unix and "C:\\" on Windows for example). - """ - return os.path.splitdrive(str(p))[1] == os.sep diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/debugging.py b/tests/venv2/lib/python3.11/site-packages/_pytest/debugging.py deleted file mode 100644 index b256f83..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/debugging.py +++ /dev/null @@ -1,404 +0,0 @@ -# mypy: allow-untyped-defs -# ruff: noqa: T100 -"""Interactive debugging with PDB, the Python Debugger.""" - -from __future__ import annotations - -import argparse -from collections.abc import Callable -from collections.abc import Generator -import functools -import importlib -import sys -import types -from typing import Any - -from _pytest import outcomes -from _pytest._code import ExceptionInfo -from _pytest.capture import CaptureManager -from _pytest.config import Config -from _pytest.config import ConftestImportFailure -from _pytest.config import hookimpl -from _pytest.config import PytestPluginManager -from _pytest.config.argparsing import Parser -from _pytest.config.exceptions import UsageError -from _pytest.nodes import Node -from _pytest.reports import BaseReport -from _pytest.runner import CallInfo - - -def _validate_usepdb_cls(value: str) -> tuple[str, str]: - """Validate syntax of --pdbcls option.""" - try: - modname, classname = value.split(":") - except ValueError as e: - raise argparse.ArgumentTypeError( - f"{value!r} is not in the format 'modname:classname'" - ) from e - return (modname, classname) - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group.addoption( - "--pdb", - dest="usepdb", - action="store_true", - help="Start the interactive Python debugger on errors or KeyboardInterrupt", - ) - group.addoption( - "--pdbcls", - dest="usepdb_cls", - metavar="modulename:classname", - type=_validate_usepdb_cls, - help="Specify a custom interactive Python debugger for use with --pdb." - "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb", - ) - group.addoption( - "--trace", - dest="trace", - action="store_true", - help="Immediately break when running each test", - ) - - -def pytest_configure(config: Config) -> None: - import pdb - - if config.getvalue("trace"): - config.pluginmanager.register(PdbTrace(), "pdbtrace") - if config.getvalue("usepdb"): - config.pluginmanager.register(PdbInvoke(), "pdbinvoke") - - pytestPDB._saved.append( - (pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config) - ) - pdb.set_trace = pytestPDB.set_trace - pytestPDB._pluginmanager = config.pluginmanager - pytestPDB._config = config - - # NOTE: not using pytest_unconfigure, since it might get called although - # pytest_configure was not (if another plugin raises UsageError). - def fin() -> None: - ( - pdb.set_trace, - pytestPDB._pluginmanager, - pytestPDB._config, - ) = pytestPDB._saved.pop() - - config.add_cleanup(fin) - - -class pytestPDB: - """Pseudo PDB that defers to the real pdb.""" - - _pluginmanager: PytestPluginManager | None = None - _config: Config | None = None - _saved: list[ - tuple[Callable[..., None], PytestPluginManager | None, Config | None] - ] = [] - _recursive_debug = 0 - _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None - - @classmethod - def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: - if capman: - return capman.is_capturing() - return False - - @classmethod - def _import_pdb_cls(cls, capman: CaptureManager | None): - if not cls._config: - import pdb - - # Happens when using pytest.set_trace outside of a test. - return pdb.Pdb - - usepdb_cls = cls._config.getvalue("usepdb_cls") - - if cls._wrapped_pdb_cls and cls._wrapped_pdb_cls[0] == usepdb_cls: - return cls._wrapped_pdb_cls[1] - - if usepdb_cls: - modname, classname = usepdb_cls - - try: - mod = importlib.import_module(modname) - - # Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp). - parts = classname.split(".") - pdb_cls = getattr(mod, parts[0]) - for part in parts[1:]: - pdb_cls = getattr(pdb_cls, part) - except Exception as exc: - value = ":".join((modname, classname)) - raise UsageError( - f"--pdbcls: could not import {value!r}: {exc}" - ) from exc - else: - import pdb - - pdb_cls = pdb.Pdb - - wrapped_cls = cls._get_pdb_wrapper_class(pdb_cls, capman) - cls._wrapped_pdb_cls = (usepdb_cls, wrapped_cls) - return wrapped_cls - - @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): - import _pytest.config - - class PytestPdbWrapper(pdb_cls): - _pytest_capman = capman - _continued = False - - def do_debug(self, arg): - cls._recursive_debug += 1 - ret = super().do_debug(arg) - cls._recursive_debug -= 1 - return ret - - if hasattr(pdb_cls, "do_debug"): - do_debug.__doc__ = pdb_cls.do_debug.__doc__ - - def do_continue(self, arg): - ret = super().do_continue(arg) - if cls._recursive_debug == 0: - assert cls._config is not None - tw = _pytest.config.create_terminal_writer(cls._config) - tw.line() - - capman = self._pytest_capman - capturing = pytestPDB._is_capturing(capman) - if capturing: - if capturing == "global": - tw.sep(">", "PDB continue (IO-capturing resumed)") - else: - tw.sep( - ">", - f"PDB continue (IO-capturing resumed for {capturing})", - ) - assert capman is not None - capman.resume() - else: - tw.sep(">", "PDB continue") - assert cls._pluginmanager is not None - cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self) - self._continued = True - return ret - - if hasattr(pdb_cls, "do_continue"): - do_continue.__doc__ = pdb_cls.do_continue.__doc__ - - do_c = do_cont = do_continue - - def do_quit(self, arg): - # Raise Exit outcome when quit command is used in pdb. - # - # This is a bit of a hack - it would be better if BdbQuit - # could be handled, but this would require to wrap the - # whole pytest run, and adjust the report etc. - ret = super().do_quit(arg) - - if cls._recursive_debug == 0: - outcomes.exit("Quitting debugger") - - return ret - - if hasattr(pdb_cls, "do_quit"): - do_quit.__doc__ = pdb_cls.do_quit.__doc__ - - do_q = do_quit - do_exit = do_quit - - def setup(self, f, tb): - """Suspend on setup(). - - Needed after do_continue resumed, and entering another - breakpoint again. - """ - ret = super().setup(f, tb) - if not ret and self._continued: - # pdb.setup() returns True if the command wants to exit - # from the interaction: do not suspend capturing then. - if self._pytest_capman: - self._pytest_capman.suspend_global_capture(in_=True) - return ret - - def get_stack(self, f, t): - stack, i = super().get_stack(f, t) - if f is None: - # Find last non-hidden frame. - i = max(0, len(stack) - 1) - while i and stack[i][0].f_locals.get("__tracebackhide__", False): - i -= 1 - return stack, i - - return PytestPdbWrapper - - @classmethod - def _init_pdb(cls, method, *args, **kwargs): - """Initialize PDB debugging, dropping any IO capturing.""" - import _pytest.config - - if cls._pluginmanager is None: - capman: CaptureManager | None = None - else: - capman = cls._pluginmanager.getplugin("capturemanager") - if capman: - capman.suspend(in_=True) - - if cls._config: - tw = _pytest.config.create_terminal_writer(cls._config) - tw.line() - - if cls._recursive_debug == 0: - # Handle header similar to pdb.set_trace in py37+. - header = kwargs.pop("header", None) - if header is not None: - tw.sep(">", header) - else: - capturing = cls._is_capturing(capman) - if capturing == "global": - tw.sep(">", f"PDB {method} (IO-capturing turned off)") - elif capturing: - tw.sep( - ">", - f"PDB {method} (IO-capturing turned off for {capturing})", - ) - else: - tw.sep(">", f"PDB {method}") - - _pdb = cls._import_pdb_cls(capman)(**kwargs) - - if cls._pluginmanager: - cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) - return _pdb - - @classmethod - def set_trace(cls, *args, **kwargs) -> None: - """Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing.""" - frame = sys._getframe().f_back - _pdb = cls._init_pdb("set_trace", *args, **kwargs) - _pdb.set_trace(frame) - - -class PdbInvoke: - def pytest_exception_interact( - self, node: Node, call: CallInfo[Any], report: BaseReport - ) -> None: - capman = node.config.pluginmanager.getplugin("capturemanager") - if capman: - capman.suspend_global_capture(in_=True) - out, err = capman.read_global_capture() - sys.stdout.write(out) - sys.stdout.write(err) - assert call.excinfo is not None - _enter_pdb(node, call.excinfo, report) - - def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: - exc_or_tb = _postmortem_exc_or_tb(excinfo) - post_mortem(exc_or_tb) - - -class PdbTrace: - @hookimpl(wrapper=True) - def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]: - wrap_pytest_function_for_tracing(pyfuncitem) - return (yield) - - -def wrap_pytest_function_for_tracing(pyfuncitem) -> None: - """Change the Python function object of the given Function item by a - wrapper which actually enters pdb before calling the python function - itself, effectively leaving the user in the pdb prompt in the first - statement of the function.""" - _pdb = pytestPDB._init_pdb("runcall") - testfunction = pyfuncitem.obj - - # we can't just return `partial(pdb.runcall, testfunction)` because (on - # python < 3.7.4) runcall's first param is `func`, which means we'd get - # an exception if one of the kwargs to testfunction was called `func`. - @functools.wraps(testfunction) - def wrapper(*args, **kwargs) -> None: - func = functools.partial(testfunction, *args, **kwargs) - _pdb.runcall(func) - - pyfuncitem.obj = wrapper - - -def maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None: - """Wrap the given pytestfunct item for tracing support if --trace was given in - the command line.""" - if pyfuncitem.config.getvalue("trace"): - wrap_pytest_function_for_tracing(pyfuncitem) - - -def _enter_pdb( - node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport -) -> BaseReport: - # XXX we reuse the TerminalReporter's terminalwriter - # because this seems to avoid some encoding related troubles - # for not completely clear reasons. - tw = node.config.pluginmanager.getplugin("terminalreporter")._tw - tw.line() - - showcapture = node.config.option.showcapture - - for sectionname, content in ( - ("stdout", rep.capstdout), - ("stderr", rep.capstderr), - ("log", rep.caplog), - ): - if showcapture in (sectionname, "all") and content: - tw.sep(">", "captured " + sectionname) - if content[-1:] == "\n": - content = content[:-1] - tw.line(content) - - tw.sep(">", "traceback") - rep.toterminal(tw) - tw.sep(">", "entering PDB") - tb_or_exc = _postmortem_exc_or_tb(excinfo) - rep._pdbshown = True # type: ignore[attr-defined] - post_mortem(tb_or_exc) - return rep - - -def _postmortem_exc_or_tb( - excinfo: ExceptionInfo[BaseException], -) -> types.TracebackType | BaseException: - from doctest import UnexpectedException - - get_exc = sys.version_info >= (3, 13) - if isinstance(excinfo.value, UnexpectedException): - # A doctest.UnexpectedException is not useful for post_mortem. - # Use the underlying exception instead: - underlying_exc = excinfo.value - if get_exc: - return underlying_exc.exc_info[1] - - return underlying_exc.exc_info[2] - elif isinstance(excinfo.value, ConftestImportFailure): - # A config.ConftestImportFailure is not useful for post_mortem. - # Use the underlying exception instead: - cause = excinfo.value.cause - if get_exc: - return cause - - assert cause.__traceback__ is not None - return cause.__traceback__ - else: - assert excinfo._excinfo is not None - if get_exc: - return excinfo._excinfo[1] - - return excinfo._excinfo[2] - - -def post_mortem(tb_or_exc: types.TracebackType | BaseException) -> None: - p = pytestPDB._init_pdb("post_mortem") - p.reset() - p.interaction(None, tb_or_exc) - if p.quitting: - outcomes.exit("Quitting debugger") diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/deprecated.py b/tests/venv2/lib/python3.11/site-packages/_pytest/deprecated.py deleted file mode 100644 index 95e75e6..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/deprecated.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Deprecation messages and bits of code used elsewhere in the codebase that -is planned to be removed in the next pytest release. - -Keeping it in a central location makes it easy to track what is deprecated and should -be removed when the time comes. - -All constants defined in this module should be either instances of -:class:`PytestWarning`, or :class:`UnformattedWarning` -in case of warnings which need to format their messages. -""" - -from __future__ import annotations - -from warnings import warn - -from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import PytestRemovedIn10Warning -from _pytest.warning_types import UnformattedWarning - - -# set of plugins which have been integrated into the core; we use this list to ignore -# them during registration to avoid conflicts -DEPRECATED_EXTERNAL_PLUGINS = { - "pytest_catchlog", - "pytest_capturelog", - "pytest_faulthandler", - "pytest_subtests", -} - - -# This could have been removed pytest 8, but it's harmless and common, so no rush to remove. -YIELD_FIXTURE = PytestDeprecationWarning( - "@pytest.yield_fixture is deprecated.\n" - "Use @pytest.fixture instead; they are the same." -) - -CLASS_FIXTURE_INSTANCE_METHOD = PytestRemovedIn10Warning( - "Class-scoped fixture defined as instance method is deprecated.\n" - "Instance attributes set in this fixture will NOT be visible to test methods,\n" - "as each test gets a new instance while the fixture runs only once per class.\n" - "Use @classmethod decorator and set attributes on cls instead.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method" -) - -# This deprecation is never really meant to be removed. -PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") - - -HOOK_LEGACY_MARKING = UnformattedWarning( - PytestRemovedIn10Warning, - "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" - "Please use the pytest.hook{type}({hook_opts}) decorator instead\n" - " to configure the hooks.\n" - " See https://docs.pytest.org/en/latest/deprecations.html" - "#configuring-hook-specs-impls-using-markers", -) - -MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES = PytestRemovedIn10Warning( - "monkeypatch.syspath_prepend() called with pkg_resources legacy namespace packages detected.\n" - "Legacy namespace packages (using pkg_resources.declare_namespace) are deprecated.\n" - "Please use native namespace packages (PEP 420) instead.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#monkeypatch-fixup-namespace-packages" -) - -PARAMETRIZE_NON_COLLECTION_ITERABLE = UnformattedWarning( - PytestRemovedIn10Warning, - "Passing a non-Collection iterable to parametrize is deprecated.\n" - "Test: {nodeid}, argvalues type: {type_name}\n" - "Please convert to a list or tuple.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#parametrize-iterators", -) - -CONSOLE_MAIN = PytestRemovedIn10Warning( - "pytest.console_main() is deprecated and will be removed in pytest 10.\n" - "It was never intended for programmatic use; use pytest.main() instead.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#console-main" -) - -CONFIG_INICFG = PytestRemovedIn10Warning( - "config.inicfg is deprecated, use config.getini() to access configuration values instead.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#config-inicfg" -) - -FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN = UnformattedWarning( - PytestRemovedIn10Warning, - 'Calling request.getfixturevalue("{argname}") during teardown is deprecated.\n' - "Please request the fixture before teardown begins, either by declaring it in the fixture signature " - "or by calling request.getfixturevalue() before the fixture yields.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#dynamic-fixture-request-during-teardown", -) - -PASTEBIN = PytestRemovedIn10Warning( - "The --pastebin option is deprecated. " - "The functionality is now available in an external plugin package, pytest-pastebin.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#the-pastebin-option" -) - -# You want to make some `__init__` or function "private". -# -# def my_private_function(some, args): -# ... -# -# Do this: -# -# def my_private_function(some, args, *, _ispytest: bool = False): -# check_ispytest(_ispytest) -# ... -# -# Change all internal/allowed calls to -# -# my_private_function(some, args, _ispytest=True) -# -# All other calls will get the default _ispytest=False and trigger -# the warning (possibly error in the future). - - -FIXTURE_BASEID_DEPRECATED = PytestRemovedIn10Warning( - "Passing baseid to FixtureDef is deprecated. Pass node instead for fixture scoping." -) - -FIXTURE_NODEID_DEPRECATED = PytestRemovedIn10Warning( - "Passing nodeid to _register_fixture is deprecated. " - "Pass node instead for fixture scoping." -) - -FIXTUREDEF_HAS_LOCATION_DEPRECATED = PytestRemovedIn10Warning( - "FixtureDef.has_location is deprecated and will be removed in pytest 10. " - "See https://docs.pytest.org/en/stable/deprecations.html#fixturedef-has-location-deprecated" -) - -PARSEFACTORIES_NODEID_DEPRECATED = PytestRemovedIn10Warning( - "Passing nodeid string to parsefactories is deprecated. " - "Use parsefactories(holder=obj, node=node) instead." -) - - -def check_ispytest(ispytest: bool) -> None: - if not ispytest: - warn(PRIVATE, stacklevel=3) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/doctest.py b/tests/venv2/lib/python3.11/site-packages/_pytest/doctest.py deleted file mode 100644 index b1f3651..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/doctest.py +++ /dev/null @@ -1,735 +0,0 @@ -# mypy: allow-untyped-defs -"""Discover and run doctests in modules and test files.""" - -from __future__ import annotations - -import bdb -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Sequence -from contextlib import contextmanager -import functools -import inspect -import os -from pathlib import Path -import platform -import re -import sys -import traceback -import types -from typing import Any -from typing import TYPE_CHECKING -import warnings - -from _pytest import outcomes -from _pytest._code.code import ExceptionInfo -from _pytest._code.code import ReprFileLocation -from _pytest._code.code import TerminalRepr -from _pytest._io import TerminalWriter -from _pytest.compat import safe_getattr -from _pytest.config import Config -from _pytest.config.argparsing import Parser -from _pytest.fixtures import fixture -from _pytest.fixtures import TopRequest -from _pytest.nodes import Collector -from _pytest.nodes import Item -from _pytest.outcomes import OutcomeException -from _pytest.outcomes import skip -from _pytest.pathlib import fnmatch_ex -from _pytest.python import Module -from _pytest.python_api import approx -from _pytest.warning_types import PytestWarning - - -if TYPE_CHECKING: - import doctest - - from typing_extensions import Self - -DOCTEST_REPORT_CHOICE_NONE = "none" -DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" -DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" -DOCTEST_REPORT_CHOICE_UDIFF = "udiff" -DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure" - -DOCTEST_REPORT_CHOICES = ( - DOCTEST_REPORT_CHOICE_NONE, - DOCTEST_REPORT_CHOICE_CDIFF, - DOCTEST_REPORT_CHOICE_NDIFF, - DOCTEST_REPORT_CHOICE_UDIFF, - DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, -) - -# Lazy definition of runner class -RUNNER_CLASS = None -# Lazy definition of output checker class -CHECKER_CLASS: type[doctest.OutputChecker] | None = None - - -def pytest_addoption(parser: Parser) -> None: - parser.addini( - "doctest_optionflags", - "Option flags for doctests", - type="args", - default=["ELLIPSIS"], - ) - parser.addini( - "doctest_encoding", "Encoding used for doctest files", default="utf-8" - ) - group = parser.getgroup("collect") - group.addoption( - "--doctest-modules", - action="store_true", - default=False, - help="Run doctests in all .py modules", - dest="doctestmodules", - ) - group.addoption( - "--doctest-report", - type=str.lower, - default="udiff", - help="Choose another output format for diffs on doctest failure", - choices=DOCTEST_REPORT_CHOICES, - dest="doctestreport", - ) - group.addoption( - "--doctest-glob", - action="append", - default=[], - metavar="pat", - help="Doctests file matching pattern, default: test*.txt", - dest="doctestglob", - ) - group.addoption( - "--doctest-ignore-import-errors", - action="store_true", - default=False, - help="Ignore doctest collection errors", - dest="doctest_ignore_import_errors", - ) - group.addoption( - "--doctest-continue-on-failure", - action="store_true", - default=False, - help="For a given doctest, continue to run after the first failure", - dest="doctest_continue_on_failure", - ) - - -def pytest_unconfigure() -> None: - global RUNNER_CLASS - - RUNNER_CLASS = None - - -def pytest_collect_file( - file_path: Path, - parent: Collector, -) -> DoctestModule | DoctestTextfile | None: - config = parent.config - if file_path.suffix == ".py": - if config.option.doctestmodules and not any( - (_is_setup_py(file_path), _is_main_py(file_path)) - ): - return DoctestModule.from_parent(parent, path=file_path) - elif _is_doctest(config, file_path, parent): - return DoctestTextfile.from_parent(parent, path=file_path) - return None - - -def _is_setup_py(path: Path) -> bool: - if path.name != "setup.py": - return False - contents = path.read_bytes() - return b"setuptools" in contents or b"distutils" in contents - - -def _is_doctest(config: Config, path: Path, parent: Collector) -> bool: - if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path): - return True - globs = config.getoption("doctestglob") or ["test*.txt"] - return any(fnmatch_ex(glob, path) for glob in globs) - - -def _is_main_py(path: Path) -> bool: - return path.name == "__main__.py" - - -class ReprFailDoctest(TerminalRepr): - def __init__( - self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] - ) -> None: - self.reprlocation_lines = reprlocation_lines - - def toterminal(self, tw: TerminalWriter) -> None: - for reprlocation, lines in self.reprlocation_lines: - for line in lines: - tw.line(line) - reprlocation.toterminal(tw) - - -class MultipleDoctestFailures(Exception): - def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: - super().__init__() - self.failures = failures - - -def _init_runner_class() -> type[doctest.DocTestRunner]: - import doctest - - class PytestDoctestRunner(doctest.DebugRunner): - """Runner to collect failures. - - Note that the out variable in this case is a list instead of a - stdout-like object. - """ - - def __init__( - self, - checker: doctest.OutputChecker | None = None, - verbose: bool | None = None, - optionflags: int = 0, - continue_on_failure: bool = True, - ) -> None: - super().__init__(checker=checker, verbose=verbose, optionflags=optionflags) - self.continue_on_failure = continue_on_failure - - def report_failure( - self, - out, - test: doctest.DocTest, - example: doctest.Example, - got: str, - ) -> None: - failure = doctest.DocTestFailure(test, example, got) - if self.continue_on_failure: - out.append(failure) - else: - raise failure - - def report_unexpected_exception( - self, - out, - test: doctest.DocTest, - example: doctest.Example, - exc_info: tuple[type[BaseException], BaseException, types.TracebackType], - ) -> None: - if isinstance(exc_info[1], OutcomeException): - raise exc_info[1] - if isinstance(exc_info[1], bdb.BdbQuit): - outcomes.exit("Quitting debugger") - failure = doctest.UnexpectedException(test, example, exc_info) - if self.continue_on_failure: - out.append(failure) - else: - raise failure - - return PytestDoctestRunner - - -def _get_runner( - checker: doctest.OutputChecker | None = None, - verbose: bool | None = None, - optionflags: int = 0, - continue_on_failure: bool = True, -) -> doctest.DocTestRunner: - # We need this in order to do a lazy import on doctest - global RUNNER_CLASS - if RUNNER_CLASS is None: - RUNNER_CLASS = _init_runner_class() - # Type ignored because the continue_on_failure argument is only defined on - # PytestDoctestRunner, which is lazily defined so can't be used as a type. - return RUNNER_CLASS( # type: ignore - checker=checker, - verbose=verbose, - optionflags=optionflags, - continue_on_failure=continue_on_failure, - ) - - -class DoctestItem(Item): - def __init__( - self, - name: str, - parent: DoctestTextfile | DoctestModule, - runner: doctest.DocTestRunner, - dtest: doctest.DocTest, - ) -> None: - super().__init__(name, parent) - self.runner = runner - self.dtest = dtest - - # Stuff needed for fixture support. - self.obj = None - fm = self.session._fixturemanager - fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None) - self._fixtureinfo = fixtureinfo - self.fixturenames = fixtureinfo.names_closure - self._initrequest() - - @classmethod - def from_parent( # type: ignore[override] - cls, - parent: DoctestTextfile | DoctestModule, - *, - name: str, - runner: doctest.DocTestRunner, - dtest: doctest.DocTest, - ) -> Self: - # incompatible signature due to imposed limits on subclass - """The public named constructor.""" - return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) - - def _initrequest(self) -> None: - self.funcargs: dict[str, object] = {} - self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] - - def setup(self) -> None: - self._request._fillfixtures() - globs = dict(getfixture=self._request.getfixturevalue) - for name, value in self._request.getfixturevalue("doctest_namespace").items(): - globs[name] = value - self.dtest.globs.update(globs) - - def runtest(self) -> None: - _check_all_skipped(self.dtest) - self._disable_output_capturing_for_darwin() - failures: list[doctest.DocTestFailure] = [] - # Type ignored because we change the type of `out` from what - # doctest expects. - self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] - if failures: - raise MultipleDoctestFailures(failures) - - def _disable_output_capturing_for_darwin(self) -> None: - """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" - if platform.system() != "Darwin": - return - capman = self.config.pluginmanager.getplugin("capturemanager") - if capman: - capman.suspend_global_capture(in_=True) - out, err = capman.read_global_capture() - sys.stdout.write(out) - sys.stderr.write(err) - - # TODO: Type ignored -- breaks Liskov Substitution. - def repr_failure( # type: ignore[override] - self, - excinfo: ExceptionInfo[BaseException], - ) -> str | TerminalRepr: - import doctest - - failures: ( - Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None - ) = None - if isinstance( - excinfo.value, doctest.DocTestFailure | doctest.UnexpectedException - ): - failures = [excinfo.value] - elif isinstance(excinfo.value, MultipleDoctestFailures): - failures = excinfo.value.failures - - if failures is None: - return super().repr_failure(excinfo) - - reprlocation_lines = [] - for failure in failures: - example = failure.example - test = failure.test - filename = test.filename - if test.lineno is None: - lineno = None - else: - lineno = test.lineno + example.lineno + 1 - message = type(failure).__name__ - # TODO: ReprFileLocation doesn't expect a None lineno. - reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] - checker = _get_checker() - report_choice = _get_report_choice(self.config.getoption("doctestreport")) - if lineno is not None: - assert failure.test.docstring is not None - lines = failure.test.docstring.splitlines(False) - # add line numbers to the left of the error message - assert test.lineno is not None - lines = [ - f"{i + test.lineno + 1:03d} {x}" for (i, x) in enumerate(lines) - ] - # trim docstring error lines to 10 - lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] - else: - lines = [ - "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example" - ] - indent = ">>>" - for line in example.source.splitlines(): - lines.append(f"??? {indent} {line}") - indent = "..." - if isinstance(failure, doctest.DocTestFailure): - lines += checker.output_difference( - example, failure.got, report_choice - ).split("\n") - else: - inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) - lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"] - lines += [ - x.strip("\n") for x in traceback.format_exception(*failure.exc_info) - ] - reprlocation_lines.append((reprlocation, lines)) - return ReprFailDoctest(reprlocation_lines) - - def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: - return self.path, self.dtest.lineno, f"[doctest] {self.name}" - - -def _get_flag_lookup() -> dict[str, int]: - import doctest - - return dict( - DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1, - DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE, - NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE, - ELLIPSIS=doctest.ELLIPSIS, - IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, - COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, - ALLOW_UNICODE=_get_allow_unicode_flag(), - ALLOW_BYTES=_get_allow_bytes_flag(), - NUMBER=_get_number_flag(), - ) - - -def get_optionflags(config: Config) -> int: - optionflags_str = config.getini("doctest_optionflags") - flag_lookup_table = _get_flag_lookup() - flag_acc = 0 - for flag in optionflags_str: - flag_acc |= flag_lookup_table[flag] - return flag_acc - - -def _get_continue_on_failure(config: Config) -> bool: - continue_on_failure: bool = config.getvalue("doctest_continue_on_failure") - if continue_on_failure: - # We need to turn off this if we use pdb since we should stop at - # the first failure. - if config.getvalue("usepdb"): - continue_on_failure = False - return continue_on_failure - - -class DoctestTextfile(Module): - obj = None - - def collect(self) -> Iterable[DoctestItem]: - import doctest - - # Inspired by doctest.testfile; ideally we would use it directly, - # but it doesn't support passing a custom checker. - encoding = self.config.getini("doctest_encoding") - text = self.path.read_text(encoding) - filename = str(self.path) - name = self.path.name - globs = {"__name__": "__main__"} - - optionflags = get_optionflags(self.config) - - runner = _get_runner( - verbose=False, - optionflags=optionflags, - checker=_get_checker(), - continue_on_failure=_get_continue_on_failure(self.config), - ) - - parser = doctest.DocTestParser() - test = parser.get_doctest(text, globs, name, filename, 0) - if test.examples: - yield DoctestItem.from_parent( - self, name=test.name, runner=runner, dtest=test - ) - - -def _check_all_skipped(test: doctest.DocTest) -> None: - """Raise pytest.skip() if all examples in the given DocTest have the SKIP - option set.""" - import doctest - - all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) - if all_skipped: - skip("all tests skipped by +SKIP option") - - -def _is_mocked(obj: object) -> bool: - """Return if an object is possibly a mock object by checking the - existence of a highly improbable attribute.""" - return ( - safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) - is not None - ) - - -@contextmanager -def _patch_unwrap_mock_aware() -> Generator[None]: - """Context manager which replaces ``inspect.unwrap`` with a version - that's aware of mock objects and doesn't recurse into them.""" - real_unwrap = inspect.unwrap - - def _mock_aware_unwrap( - func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None - ) -> Any: - try: - if stop is None or stop is _is_mocked: - return real_unwrap(func, stop=_is_mocked) - _stop = stop - return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) - except Exception as e: - warnings.warn( - f"Got {e!r} when unwrapping {func!r}. This is usually caused " - "by a violation of Python's object protocol; see e.g. " - "https://github.com/pytest-dev/pytest/issues/5080", - PytestWarning, - ) - raise - - inspect.unwrap = _mock_aware_unwrap - try: - yield - finally: - inspect.unwrap = real_unwrap - - -class DoctestModule(Module): - def collect(self) -> Iterable[DoctestItem]: - import doctest - - class MockAwareDocTestFinder(doctest.DocTestFinder): - py_ver_info_minor = sys.version_info[:2] - is_find_lineno_broken = ( - py_ver_info_minor < (3, 11) - or (py_ver_info_minor == (3, 11) and sys.version_info.micro < 9) - or (py_ver_info_minor == (3, 12) and sys.version_info.micro < 3) - ) - if is_find_lineno_broken: - - def _find_lineno(self, obj, source_lines): - """On older Pythons, doctest code does not take into account - `@property`. https://github.com/python/cpython/issues/61648 - - Moreover, wrapped Doctests need to be unwrapped so the correct - line number is returned. #8796 - """ - if isinstance(obj, property): - obj = getattr(obj, "fget", obj) - - if hasattr(obj, "__wrapped__"): - # Get the main obj in case of it being wrapped - obj = inspect.unwrap(obj) - - # Type ignored because this is a private function. - return super()._find_lineno( # type:ignore[misc] - obj, - source_lines, - ) - - if sys.version_info < (3, 13): - - def _from_module(self, module, object): - """`cached_property` objects are never considered a part - of the 'current module'. As such they are skipped by doctest. - Here we override `_from_module` to check the underlying - function instead. https://github.com/python/cpython/issues/107995 - """ - if isinstance(object, functools.cached_property): - object = object.func - - # Type ignored because this is a private function. - return super()._from_module(module, object) # type: ignore[misc] - - try: - module = self.obj - except Collector.CollectError: - if self.config.getvalue("doctest_ignore_import_errors"): - skip(f"unable to import module {self.path!r}") - else: - raise - - # doctests supports fixtures via `getfixture` and autouse. - self.session._fixturemanager.parsefactories(self) - - # Uses internal doctest module parsing mechanism. - finder = MockAwareDocTestFinder() - optionflags = get_optionflags(self.config) - runner = _get_runner( - verbose=False, - optionflags=optionflags, - checker=_get_checker(), - continue_on_failure=_get_continue_on_failure(self.config), - ) - - for test in finder.find(module, module.__name__): - if test.examples: # skip empty doctests - yield DoctestItem.from_parent( - self, name=test.name, runner=runner, dtest=test - ) - - -def _init_checker_class() -> type[doctest.OutputChecker]: - import doctest - - class LiteralsOutputChecker(doctest.OutputChecker): - # Based on doctest_nose_plugin.py from the nltk project - # (https://github.com/nltk/nltk) and on the "numtest" doctest extension - # by Sebastien Boisgerault (https://github.com/boisgera/numtest). - - _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) - _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) - _number_re = re.compile( - r""" - (?P - (?P - (?P [+-]?\d*)\.(?P\d+) - | - (?P [+-]?\d+)\. - ) - (?: - [Ee] - (?P [+-]?\d+) - )? - | - (?P [+-]?\d+) - (?: - [Ee] - (?P [+-]?\d+) - ) - ) - """, - re.VERBOSE, - ) - - def check_output(self, want: str, got: str, optionflags: int) -> bool: - if super().check_output(want, got, optionflags): - return True - - allow_unicode = optionflags & _get_allow_unicode_flag() - allow_bytes = optionflags & _get_allow_bytes_flag() - allow_number = optionflags & _get_number_flag() - - if not allow_unicode and not allow_bytes and not allow_number: - return False - - def remove_prefixes(regex: re.Pattern[str], txt: str) -> str: - return re.sub(regex, r"\1\2", txt) - - if allow_unicode: - want = remove_prefixes(self._unicode_literal_re, want) - got = remove_prefixes(self._unicode_literal_re, got) - - if allow_bytes: - want = remove_prefixes(self._bytes_literal_re, want) - got = remove_prefixes(self._bytes_literal_re, got) - - if allow_number: - got = self._remove_unwanted_precision(want, got) - - return super().check_output(want, got, optionflags) - - def _remove_unwanted_precision(self, want: str, got: str) -> str: - wants = list(self._number_re.finditer(want)) - gots = list(self._number_re.finditer(got)) - if len(wants) != len(gots): - return got - offset = 0 - for w, g in zip(wants, gots, strict=True): - fraction: str | None = w.group("fraction") - exponent: str | None = w.group("exponent1") - if exponent is None: - exponent = w.group("exponent2") - precision = 0 if fraction is None else len(fraction) - if exponent is not None: - precision -= int(exponent) - if float(w.group()) == approx(float(g.group()), abs=10**-precision): - # They're close enough. Replace the text we actually - # got with the text we want, so that it will match when we - # check the string literally. - got = ( - got[: g.start() + offset] + w.group() + got[g.end() + offset :] - ) - offset += w.end() - w.start() - (g.end() - g.start()) - return got - - return LiteralsOutputChecker - - -def _get_checker() -> doctest.OutputChecker: - """Return a doctest.OutputChecker subclass that supports some - additional options: - - * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' - prefixes (respectively) in string literals. Useful when the same - doctest should run in Python 2 and Python 3. - - * NUMBER to ignore floating-point differences smaller than the - precision of the literal number in the doctest. - - An inner class is used to avoid importing "doctest" at the module - level. - """ - global CHECKER_CLASS - if CHECKER_CLASS is None: - CHECKER_CLASS = _init_checker_class() - return CHECKER_CLASS() - - -def _get_allow_unicode_flag() -> int: - """Register and return the ALLOW_UNICODE flag.""" - import doctest - - return doctest.register_optionflag("ALLOW_UNICODE") - - -def _get_allow_bytes_flag() -> int: - """Register and return the ALLOW_BYTES flag.""" - import doctest - - return doctest.register_optionflag("ALLOW_BYTES") - - -def _get_number_flag() -> int: - """Register and return the NUMBER flag.""" - import doctest - - return doctest.register_optionflag("NUMBER") - - -def _get_report_choice(key: str) -> int: - """Return the actual `doctest` module flag value. - - We want to do it as late as possible to avoid importing `doctest` and all - its dependencies when parsing options, as it adds overhead and breaks tests. - """ - import doctest - - return { - DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF, - DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF, - DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF, - DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE, - DOCTEST_REPORT_CHOICE_NONE: 0, - }[key] - - -@fixture(scope="session") -def doctest_namespace() -> dict[str, Any]: - """Fixture that returns a :py:class:`dict` that will be injected into the - namespace of doctests. - - Usually this fixture is used in conjunction with another ``autouse`` fixture: - - .. code-block:: python - - @pytest.fixture(autouse=True) - def add_np(doctest_namespace): - doctest_namespace["np"] = numpy - - For more details: :ref:`doctest_namespace`. - """ - return dict() diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/faulthandler.py b/tests/venv2/lib/python3.11/site-packages/_pytest/faulthandler.py deleted file mode 100644 index 080cf58..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/faulthandler.py +++ /dev/null @@ -1,119 +0,0 @@ -from __future__ import annotations - -from collections.abc import Generator -import os -import sys - -from _pytest.config import Config -from _pytest.config.argparsing import Parser -from _pytest.nodes import Item -from _pytest.stash import StashKey -import pytest - - -fault_handler_original_stderr_fd_key = StashKey[int]() -fault_handler_stderr_fd_key = StashKey[int]() - - -def pytest_addoption(parser: Parser) -> None: - help_timeout = ( - "Dump the traceback of all threads if a test takes " - "more than TIMEOUT seconds to finish" - ) - help_exit_on_timeout = ( - "Exit the test process if a test takes more than " - "faulthandler_timeout seconds to finish" - ) - parser.addini("faulthandler_timeout", help_timeout, default=0.0) - parser.addini( - "faulthandler_exit_on_timeout", help_exit_on_timeout, type="bool", default=False - ) - - -def pytest_configure(config: Config) -> None: - import faulthandler - - # at teardown we want to restore the original faulthandler fileno - # but faulthandler has no api to return the original fileno - # so here we stash the stderr fileno to be used at teardown - # sys.stderr and sys.__stderr__ may be closed or patched during the session - # so we can't rely on their values being good at that point (#11572). - stderr_fileno = get_stderr_fileno() - if faulthandler.is_enabled(): - config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno - config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno) - faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) - - -def pytest_unconfigure(config: Config) -> None: - import faulthandler - - faulthandler.disable() - # Close the dup file installed during pytest_configure. - if fault_handler_stderr_fd_key in config.stash: - os.close(config.stash[fault_handler_stderr_fd_key]) - del config.stash[fault_handler_stderr_fd_key] - # Re-enable the faulthandler if it was originally enabled. - if fault_handler_original_stderr_fd_key in config.stash: - faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key]) - del config.stash[fault_handler_original_stderr_fd_key] - - -def get_stderr_fileno() -> int: - try: - fileno = sys.stderr.fileno() - # The Twisted Logger will return an invalid file descriptor since it is not backed - # by an FD. So, let's also forward this to the same code path as with pytest-xdist. - if fileno == -1: - raise AttributeError() - return fileno - except (AttributeError, ValueError): - # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. - # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors - # This is potentially dangerous, but the best we can do. - assert sys.__stderr__ is not None - return sys.__stderr__.fileno() - - -def get_timeout_config_value(config: Config) -> float: - return float(config.getini("faulthandler_timeout") or 0.0) - - -def get_exit_on_timeout_config_value(config: Config) -> bool: - exit_on_timeout = config.getini("faulthandler_exit_on_timeout") - assert isinstance(exit_on_timeout, bool) - return exit_on_timeout - - -@pytest.hookimpl(wrapper=True, trylast=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: - timeout = get_timeout_config_value(item.config) - exit_on_timeout = get_exit_on_timeout_config_value(item.config) - if timeout > 0: - import faulthandler - - stderr = item.config.stash[fault_handler_stderr_fd_key] - faulthandler.dump_traceback_later(timeout, file=stderr, exit=exit_on_timeout) - try: - return (yield) - finally: - faulthandler.cancel_dump_traceback_later() - else: - return (yield) - - -@pytest.hookimpl(tryfirst=True) -def pytest_enter_pdb() -> None: - """Cancel any traceback dumping due to timeout before entering pdb.""" - import faulthandler - - faulthandler.cancel_dump_traceback_later() - - -@pytest.hookimpl(tryfirst=True) -def pytest_exception_interact() -> None: - """Cancel any traceback dumping due to an interactive exception being - raised.""" - import faulthandler - - faulthandler.cancel_dump_traceback_later() diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/fixtures.py b/tests/venv2/lib/python3.11/site-packages/_pytest/fixtures.py deleted file mode 100644 index f4ca2ea..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/fixtures.py +++ /dev/null @@ -1,2393 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import abc -from collections import defaultdict -from collections import deque -from collections import OrderedDict -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import MutableMapping -from collections.abc import Sequence -from collections.abc import Set as AbstractSet -import dataclasses -import functools -import inspect -import os -from pathlib import Path -import sys -import types -from typing import Any -from typing import cast -from typing import Final -from typing import final -from typing import Generic -from typing import Literal -from typing import NoReturn -from typing import overload -from typing import TYPE_CHECKING -from typing import TypeVar -import warnings - -import _pytest -from _pytest import nodes -from _pytest._code import getfslineno -from _pytest._code import Source -from _pytest._code.code import ExceptionInfoFormatter -from _pytest._code.code import TerminalRepr -from _pytest._io import TerminalWriter -from _pytest.compat import assert_never -from _pytest.compat import deprecated -from _pytest.compat import get_real_func -from _pytest.compat import getfuncargnames -from _pytest.compat import getimfunc -from _pytest.compat import getlocation -from _pytest.compat import NOTSET -from _pytest.compat import NotSetType -from _pytest.compat import safe_getattr -from _pytest.compat import safe_isclass -from _pytest.compat import signature -from _pytest.config import _PluggyPlugin -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.deprecated import CLASS_FIXTURE_INSTANCE_METHOD -from _pytest.deprecated import FIXTURE_BASEID_DEPRECATED -from _pytest.deprecated import FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN -from _pytest.deprecated import FIXTURE_NODEID_DEPRECATED -from _pytest.deprecated import FIXTUREDEF_HAS_LOCATION_DEPRECATED -from _pytest.deprecated import PARSEFACTORIES_NODEID_DEPRECATED -from _pytest.deprecated import YIELD_FIXTURE -from _pytest.main import Session -from _pytest.mark import Mark -from _pytest.mark import ParameterSet -from _pytest.mark.structures import MarkDecorator -from _pytest.outcomes import fail -from _pytest.outcomes import skip -from _pytest.outcomes import TEST_OUTCOME -from _pytest.pathlib import absolutepath -from _pytest.pathlib import bestrelpath -from _pytest.scope import HIGH_SCOPES -from _pytest.scope import Scope -from _pytest.scope import ScopeName -from _pytest.warning_types import PytestWarning - - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - - -if TYPE_CHECKING: - from _pytest.python import CallSpec2 - from _pytest.python import Function - from _pytest.python import Metafunc - from _pytest.reports import CollectReport - - -# The value of the fixture -- return/yield of the fixture function (type variable). -FixtureValue = TypeVar("FixtureValue", covariant=True) -# The type of the fixture function (type alias). -FixtureFunction = Callable[..., object] -# The type of a fixture function (type alias generic in fixture value). -_FixtureFunc = Callable[..., FixtureValue] | Callable[..., Generator[FixtureValue]] -# The type of FixtureDef.cached_result (type alias generic in fixture value). -_FixtureCachedResult = ( - tuple[ - # The result. - FixtureValue, - # Cache key. - object, - None, - ] - | tuple[ - None, - # Cache key. - object, - # The exception and the original traceback. - tuple[BaseException, types.TracebackType | None], - ] -) - - -def pytest_sessionstart(session: Session) -> None: - session._fixturemanager = FixtureManager(session) - - -def get_scope_package( - node: nodes.Item, - fixturedef: FixtureDef[object], -) -> nodes.Node | None: - from _pytest.python import Package - - for parent in node.iter_parents(): - if isinstance(parent, Package): - if fixturedef.node is not None: - if parent == fixturedef.node: - return parent - else: - if parent.nodeid == fixturedef.baseid: - return parent - return node.session - - -def is_visibility_more_specific( - candidate: FixtureDef[Any], other: FixtureDef[Any] -) -> bool: - """Return whether the visibility of ``candidate`` is strictly more specific - than that of ``other``, i.e. ``candidate`` is defined on a strict descendant - in the collection tree of where ``other`` is defined.""" - if candidate.node is None or other.node is None: - # Fallback for fixtures registered with a string nodeid (deprecated). - # In this case compare baseids, which are nodeid prefixes. - # This branch can be removed once baseid deprecation is done (pytest 10). - if candidate.baseid == other.baseid: - return False - if other.baseid == "": - return True - # `candidate.baseid` must continue with a node separator for it to be a - # true descendant. - return candidate.baseid.startswith(other.baseid) and candidate.baseid[ - len(other.baseid) - ] in ("/", ":") - - return ( - candidate.node is not other.node and other.node in candidate.node.iter_parents() - ) - - -def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: - """Get the closest parent node (including self) which matches the given - scope. - - If there is no parent node for the scope (e.g. asking for class scope on a - Module, or on a Function when not defined in a class), returns None. - """ - import _pytest.python - - if scope is Scope.Function: - # Type ignored because this is actually safe, see: - # https://github.com/python/mypy/issues/4717 - return node.getparent(nodes.Item) # type: ignore[type-abstract] - elif scope is Scope.Class: - return node.getparent(_pytest.python.Class) - elif scope is Scope.Module: - return node.getparent(_pytest.python.Module) - elif scope is Scope.Package: - return node.getparent(_pytest.python.Package) - elif scope is Scope.Session: - return node.getparent(_pytest.main.Session) - else: - assert_never(scope) - - -# TODO: Try to use FixtureFunctionDefinition instead of the marker -def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: - """Return fixturemarker or None if it doesn't exist""" - if isinstance(obj, FixtureFunctionDefinition): - return obj._fixture_function_marker - return None - - -# Algorithm for sorting on a per-parametrized resource setup basis. -# It is called for Session scope first and performs sorting -# down to the lower scopes such as to minimize number of "high scope" -# setups and teardowns. - - -@dataclasses.dataclass(frozen=True) -class ParamArgKey: - """A key for a high-scoped parameter used by an item. - - For use as a hashable key in `reorder_items`. The combination of fields - is meant to uniquely identify a particular "instance" of a param, - potentially shared by multiple items in a scope. - """ - - #: The param name. - argname: str - param_index: int - #: For scopes Package, Module, Class, the path to the file (directory in - #: Package's case) of the package/module/class where the item is defined. - scoped_item_path: Path | None - #: For Class scope, the class where the item is defined. - item_cls: type | None - - -_V = TypeVar("_V") -OrderedSet = dict[_V, None] - - -def get_param_argkeys(item: nodes.Item, scope: Scope) -> Iterator[ParamArgKey]: - """Return all ParamArgKeys for item matching the specified high scope.""" - assert scope is not Scope.Function - - try: - callspec: CallSpec2 = item.callspec # type: ignore[attr-defined] - except AttributeError: - return - - item_cls = None - if scope is Scope.Session: - scoped_item_path = None - elif scope is Scope.Package: - # Package key = module's directory. - scoped_item_path = item.path.parent - elif scope is Scope.Module: - scoped_item_path = item.path - elif scope is Scope.Class: - scoped_item_path = item.path - item_cls = item.cls # type: ignore[attr-defined] - else: - assert_never(scope) - - for argname in callspec.indices: - if callspec._arg2scope[argname] != scope: - continue - param_index = callspec.indices[argname] - yield ParamArgKey(argname, param_index, scoped_item_path, item_cls) - - -def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]: - argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[ParamArgKey]]] = {} - items_by_argkey: dict[Scope, dict[ParamArgKey, OrderedDict[nodes.Item, None]]] = {} - for scope in HIGH_SCOPES: - scoped_argkeys_by_item = argkeys_by_item[scope] = {} - scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(OrderedDict) - for item in items: - argkeys = dict.fromkeys(get_param_argkeys(item, scope)) - if argkeys: - scoped_argkeys_by_item[item] = argkeys - for argkey in argkeys: - scoped_items_by_argkey[argkey][item] = None - - items_set = dict.fromkeys(items) - return list( - reorder_items_atscope( - items_set, argkeys_by_item, items_by_argkey, Scope.Session - ) - ) - - -def reorder_items_atscope( - items: OrderedSet[nodes.Item], - argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[ParamArgKey]]], - items_by_argkey: Mapping[ - Scope, Mapping[ParamArgKey, OrderedDict[nodes.Item, None]] - ], - scope: Scope, -) -> OrderedSet[nodes.Item]: - if scope is Scope.Function or len(items) < 3: - return items - - scoped_items_by_argkey = items_by_argkey[scope] - scoped_argkeys_by_item = argkeys_by_item[scope] - - ignore: set[ParamArgKey] = set() - items_deque = deque(items) - items_done: OrderedSet[nodes.Item] = {} - while items_deque: - no_argkey_items: OrderedSet[nodes.Item] = {} - slicing_argkey = None - while items_deque: - item = items_deque.popleft() - if item in items_done or item in no_argkey_items: - continue - argkeys = dict.fromkeys( - k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore - ) - if not argkeys: - no_argkey_items[item] = None - else: - slicing_argkey, _ = argkeys.popitem() - # We don't have to remove relevant items from later in the - # deque because they'll just be ignored. - matching_items = [ - i for i in scoped_items_by_argkey[slicing_argkey] if i in items - ] - for i in reversed(matching_items): - items_deque.appendleft(i) - # Fix items_by_argkey order. - for other_scope in HIGH_SCOPES: - other_scoped_items_by_argkey = items_by_argkey[other_scope] - for argkey in argkeys_by_item[other_scope].get(i, ()): - argkey_dict = other_scoped_items_by_argkey[argkey] - if not hasattr(sys, "pypy_version_info"): - argkey_dict[i] = None - argkey_dict.move_to_end(i, last=False) - else: - # Work around a bug in PyPy: - # https://github.com/pypy/pypy/issues/5257 - # https://github.com/pytest-dev/pytest/issues/13312 - bkp = argkey_dict.copy() - argkey_dict.clear() - argkey_dict[i] = None - argkey_dict.update(bkp) - break - if no_argkey_items: - reordered_no_argkey_items = reorder_items_atscope( - no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower() - ) - items_done.update(reordered_no_argkey_items) - if slicing_argkey is not None: - ignore.add(slicing_argkey) - return items_done - - -def traverse_fixture_closure( - initialnames: Iterable[str], - *, - getfixturedefs: Callable[[str], Sequence[FixtureDef[Any]] | None], -) -> Iterator[str]: - """Statically traverse the fixture dependency closure in DFS order starting - from initialnames, yielding all requested fixture names (argnames). - - Each argname is only yielded once. - """ - # Track the index for each fixture name in the simulated stack. - # Needed for handling override chains correctly, similar to - # FixtureRequest._get_active_fixturedef. - # Using negative indices: -1 is the most specific (last), -2 is second to - # last, etc. - current_indices: dict[str, int] = {} - - def process_argname(argname: str) -> Iterator[str]: - index = current_indices.get(argname) - - # Optimization: already processed this argname. - if index == -1: - return - - # Only yield each argname once. - if index is None: - yield argname - current_indices[argname] = -1 - - fixturedefs = getfixturedefs(argname) - if not fixturedefs: - return - - index = current_indices.get(argname, -1) - if -index > len(fixturedefs): - # Exhausted the override chain (will error during runtest). - return - fixturedef = fixturedefs[index] - - current_indices[argname] = index - 1 - for dep in fixturedef.argnames: - yield from process_argname(dep) - current_indices[argname] = index - - for argname in initialnames: - yield from process_argname(argname) - - -@dataclasses.dataclass(frozen=True) -class FuncFixtureInfo: - """Fixture-related information for a fixture-requesting item (e.g. test - function). - - This is used to examine the fixtures which an item requests statically - (known during collection). This includes autouse fixtures, fixtures - requested by the `usefixtures` marker, fixtures requested in the function - parameters, and the transitive closure of these. - - An item may also request fixtures dynamically (using `request.getfixturevalue`); - these are not reflected here. - """ - - __slots__ = ("argnames", "initialnames", "name2fixturedefs", "names_closure") - - # Fixture names that the item requests directly by function parameters. - argnames: tuple[str, ...] - # Fixture names that the item immediately requires. These include - # argnames + fixture names specified via usefixtures and via autouse=True in - # fixture definitions. - initialnames: tuple[str, ...] - # The transitive closure of the fixture names that the item requires. - # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). - names_closure: list[str] - # A map from a fixture name in the transitive closure to the FixtureDefs - # matching the name which are applicable to this function. - # There may be multiple overriding fixtures with the same name. The - # sequence is ordered from furthest to closes to the function. - name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] - - def prune_dependency_tree(self) -> None: - """Recompute names_closure from initialnames and name2fixturedefs. - - Can only reduce names_closure, which means that the new closure will - always be a subset of the old one. The order is preserved. - - This method is needed because direct parametrization may shadow some - of the fixtures that were included in the originally built dependency - tree. In this way the dependency tree can get pruned, and the closure - of argnames may get reduced. - """ - closure = set( - traverse_fixture_closure( - self.initialnames, - getfixturedefs=self.name2fixturedefs.get, - ) - ) - self.names_closure[:] = (name for name in self.names_closure if name in closure) - - -class FixtureRequest(abc.ABC): - """The type of the ``request`` fixture. - - A request object gives access to the requesting test context and has a - ``param`` attribute in case the fixture is parametrized. - """ - - def __init__( - self, - pyfuncitem: Function, - fixturename: str | None, - arg2fixturedefs: Mapping[str, Sequence[FixtureDef[Any]]], - fixture_defs: dict[str, FixtureDef[Any]], - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - #: Fixture for which this request is being performed. - self.fixturename: Final = fixturename - self._pyfuncitem: Final = pyfuncitem - # The FixtureDefs for each fixture name statically requested by this - # item (computed during collection). Dynamically requested fixtures - # (using `request.getfixturevalue("foo")`) are not included here. - self._arg2fixturedefs: Final = arg2fixturedefs - # The evaluated argnames so far, mapping to the FixtureDef they resolved - # to. - self._fixture_defs: Final = fixture_defs - # Notes on the type of `param`: - # -`request.param` is only defined in parametrized fixtures, and will raise - # AttributeError otherwise. Python typing has no notion of "undefined", so - # this cannot be reflected in the type. - # - Technically `param` is only (possibly) defined on SubRequest, not - # FixtureRequest, but the typing of that is still in flux so this cheats. - # - In the future we might consider using a generic for the param type, but - # for now just using Any. - self.param: Any - - @property - def _fixturemanager(self) -> FixtureManager: - return self._pyfuncitem.session._fixturemanager - - @property - @abc.abstractmethod - def _scope(self) -> Scope: - raise NotImplementedError() - - @property - def scope(self) -> ScopeName: - """Scope string, one of "function", "class", "module", "package", "session".""" - return self._scope.value - - @abc.abstractmethod - def _check_scope( - self, - requested_fixturedef: FixtureDef[object], - requested_scope: Scope, - ) -> None: - raise NotImplementedError() - - @property - def fixturenames(self) -> list[str]: - """Names of all active fixtures in this request.""" - result = list(self._pyfuncitem.fixturenames) - result.extend(set(self._fixture_defs).difference(result)) - return result - - @property - @abc.abstractmethod - def node(self): - """Underlying collection node (depends on current request scope).""" - raise NotImplementedError() - - @property - def config(self) -> Config: - """The pytest config object associated with this request.""" - return self._pyfuncitem.config - - @property - def function(self): - """Test function object if the request has a per-function scope.""" - if self.scope != "function": - raise AttributeError( - f"function not available in {self.scope}-scoped context" - ) - return self._pyfuncitem.obj - - @property - def cls(self): - """Class (can be None) where the test function was collected.""" - if self.scope not in ("class", "function"): - raise AttributeError(f"cls not available in {self.scope}-scoped context") - clscol = self._pyfuncitem.getparent(_pytest.python.Class) - if clscol: - return clscol.obj - - @property - def instance(self): - """Instance (can be None) on which test function was collected.""" - if self.scope != "function": - return None - return getattr(self._pyfuncitem, "instance", None) - - @property - def module(self): - """Python module object where the test function was collected.""" - if self.scope not in ("function", "class", "module"): - raise AttributeError(f"module not available in {self.scope}-scoped context") - mod = self._pyfuncitem.getparent(_pytest.python.Module) - assert mod is not None - return mod.obj - - @property - def path(self) -> Path: - """Path where the test function was collected.""" - if self.scope not in ("function", "class", "module", "package"): - raise AttributeError(f"path not available in {self.scope}-scoped context") - return self._pyfuncitem.path - - @property - def keywords(self) -> MutableMapping[str, Any]: - """Keywords/markers dictionary for the underlying node.""" - node: nodes.Node = self.node - return node.keywords - - @property - def session(self) -> Session: - """Pytest session object.""" - return self._pyfuncitem.session - - @abc.abstractmethod - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - """Add finalizer/teardown function to be called without arguments after - the last test within the requesting test context finished execution.""" - raise NotImplementedError() - - def applymarker(self, marker: str | MarkDecorator) -> None: - """Apply a marker to a single test function invocation. - - This method is useful if you don't want to have a keyword/marker - on all function invocations. - - :param marker: - An object created by a call to ``pytest.mark.NAME(...)``. - """ - self.node.add_marker(marker) - - def raiseerror(self, msg: str | None) -> NoReturn: - """Raise a FixtureLookupError exception. - - :param msg: - An optional custom error message. - """ - raise FixtureLookupError(None, self, msg) - - def _raise_teardown_lookup_error(self, argname: str) -> NoReturn: - msg = ( - f'The fixture value for "{argname}" is not available during teardown ' - "because it was not previously requested.\n" - "Only fixtures that were already active can be retrieved during teardown.\n" - "Request the fixture before teardown begins by declaring it in the fixture " - "signature or by calling request.getfixturevalue() before the fixture yields." - ) - raise FixtureLookupError(argname, self, msg) - - def getfixturevalue(self, argname: str) -> Any: - """Dynamically run a named fixture function. - - Declaring fixtures via function argument is recommended where possible. - But if you can only decide whether to use another fixture at test - setup time, you may use this function to retrieve it inside a fixture - or test function body. - - This method can be used during the test setup phase or the test run - phase. Avoid using it during the teardown phase. - - .. versionchanged:: 9.1 - Calling ``request.getfixturevalue()`` during teardown to request a - fixture that was not already requested - :ref:`is deprecated `. - - :param argname: - The fixture name. - :raises pytest.FixtureLookupError: - If the given fixture could not be found. - """ - # Note that in addition to the use case described in the docstring, - # getfixturevalue() is also called by pytest itself during item and fixture - # setup to evaluate the fixtures that are requested statically - # (using function parameters, autouse, etc). - - fixturedef = self._get_active_fixturedef(argname) - assert fixturedef.cached_result is not None, ( - f'The fixture value for "{argname}" is not available. ' - "This can happen when the fixture has already been torn down." - ) - return fixturedef.cached_result[0] - - def _iter_chain(self) -> Iterator[SubRequest]: - """Yield all SubRequests in the chain, from self up. - - Note: does *not* yield the TopRequest. - """ - current = self - while isinstance(current, SubRequest): - yield current - current = current._parent_request - - def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]: - if argname == "request": - return RequestFixtureDef(self) - - # If we already finished computing a fixture by this name in this item, - # return it. - fixturedef = self._fixture_defs.get(argname) - if fixturedef is not None: - self._check_scope(fixturedef, fixturedef._scope) - return fixturedef - - # Find the appropriate fixturedef. - fixturedefs = self._arg2fixturedefs.get(argname, None) - if fixturedefs is None: - # We arrive here because of a dynamic call to - # getfixturevalue(argname) which was naturally - # not known at parsing/collection time. - fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem) - # No fixtures defined with this name. - if fixturedefs is None: - raise FixtureLookupError(argname, self) - # The are no fixtures with this name applicable for the function. - if not fixturedefs: - raise FixtureLookupError(argname, self) - - # A fixture may override another fixture with the same name, e.g. a - # fixture in a module can override a fixture in a conftest, a fixture in - # a class can override a fixture in the module, and so on. - # An overriding fixture can request its own name (possibly indirectly); - # in this case it gets the value of the fixture it overrides, one level - # up. - # Check how many `argname`s deep we are, and take the next one. - # `fixturedefs` is sorted from furthest to closest, so use negative - # indexing to go in reverse. - index = -1 - for request in self._iter_chain(): - if request.fixturename == argname: - index -= 1 - # If already consumed all of the available levels, fail. - if -index > len(fixturedefs): - raise FixtureLookupError(argname, self) - fixturedef = fixturedefs[index] - - # Prepare a SubRequest object for calling the fixture. - try: - callspec = self._pyfuncitem.callspec - except AttributeError: - callspec = None - if callspec is not None and argname in callspec.params: - param = callspec.params[argname] - param_index = callspec.indices[argname] - # The parametrize invocation scope overrides the fixture's scope. - scope = callspec._arg2scope[argname] - else: - param = NOTSET - param_index = 0 - scope = fixturedef._scope - self._check_fixturedef_without_param(fixturedef) - # The parametrize invocation scope only controls caching behavior while - # allowing wider-scoped fixtures to keep depending on the parametrized - # fixture. Scope control is enforced for parametrized fixtures - # by recreating the whole fixture tree on parameter change. - # Hence `fixturedef._scope`, not `scope`. - self._check_scope(fixturedef, fixturedef._scope) - subrequest = SubRequest( - self, scope, param, param_index, fixturedef, _ispytest=True - ) - - if not self.session._setupstate.is_node_active(self.node): - # TODO(pytest10.1): Remove the `warn` and `if` and call - # _raise_teardown_lookup_error unconditionally. - warnings.warn( - FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN.format(argname=argname), - stacklevel=3, - ) - if subrequest.node not in self.session._setupstate.stack: - self._raise_teardown_lookup_error(argname) - - # Make sure the fixture value is cached, running it if it isn't - fixturedef.execute(request=subrequest) - - self._fixture_defs[argname] = fixturedef - return fixturedef - - def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: - """Check that this request is allowed to execute this fixturedef without - a param.""" - funcitem = self._pyfuncitem - has_params = fixturedef.params is not None - fixtures_not_supported = getattr(funcitem, "nofuncargs", False) - if has_params and fixtures_not_supported: - msg = ( - f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n" - f"Node id: {funcitem.nodeid}\n" - f"Function type: {type(funcitem).__name__}" - ) - fail(msg, pytrace=False) - if has_params: - frame = inspect.stack()[3] - frameinfo = inspect.getframeinfo(frame[0]) - source_path = absolutepath(frameinfo.filename) - source_lineno = frameinfo.lineno - try: - source_path_str = str(source_path.relative_to(funcitem.config.rootpath)) - except ValueError: - source_path_str = str(source_path) - location = getlocation(fixturedef.func, funcitem.config.rootpath) - msg = ( - "The requested fixture has no parameter defined for test:\n" - f" {funcitem.nodeid}\n\n" - f"Requested fixture '{fixturedef.argname}' defined in:\n" - f"{location}\n\n" - f"Requested here:\n" - f"{source_path_str}:{source_lineno}" - ) - fail(msg, pytrace=False) - - def _get_fixturestack(self) -> list[FixtureDef[Any]]: - values = [request._fixturedef for request in self._iter_chain()] - values.reverse() - return values - - -@final -class TopRequest(FixtureRequest): - """The type of the ``request`` fixture in a test function.""" - - def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None: - super().__init__( - fixturename=None, - pyfuncitem=pyfuncitem, - arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs, - fixture_defs={}, - _ispytest=_ispytest, - ) - - @property - def _scope(self) -> Scope: - return Scope.Function - - def _check_scope( - self, - requested_fixturedef: FixtureDef[object], - requested_scope: Scope, - ) -> None: - # TopRequest always has function scope so always valid. - pass - - @property - def node(self): - return self._pyfuncitem - - def __repr__(self) -> str: - return f"" - - def _fillfixtures(self) -> None: - item = self._pyfuncitem - for argname in item.fixturenames: - if argname not in item.funcargs: - item.funcargs[argname] = self.getfixturevalue(argname) - - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - self.node.addfinalizer(finalizer) - - -@final -class SubRequest(FixtureRequest): - """The type of the ``request`` fixture in a fixture function requested - (transitively) by a test function.""" - - def __init__( - self, - request: FixtureRequest, - scope: Scope, - param: Any, - param_index: int, - fixturedef: FixtureDef[object], - *, - _ispytest: bool = False, - ) -> None: - super().__init__( - pyfuncitem=request._pyfuncitem, - fixturename=fixturedef.argname, - fixture_defs=request._fixture_defs, - arg2fixturedefs=request._arg2fixturedefs, - _ispytest=_ispytest, - ) - self._parent_request: Final[FixtureRequest] = request - self._fixturedef: Final[FixtureDef[object]] = fixturedef - if param is not NOTSET: - self.param = param - self.param_index: Final = param_index - self._scope_field: Final = scope - if scope is Scope.Function: - # This might also be a non-function Item despite its attribute name. - node: nodes.Node | None = self._pyfuncitem - elif scope is Scope.Package: - node = get_scope_package(self._pyfuncitem, self._fixturedef) - else: - node = get_scope_node(self._pyfuncitem, scope) - if node is None and scope is Scope.Class: - # Fallback to function item itself. - node = self._pyfuncitem - assert node, ( - f'Could not obtain a node for scope "{scope}" for function {self._pyfuncitem!r}' - ) - self._node: Final = node - - def __repr__(self) -> str: - return f"" - - @property - def _scope(self) -> Scope: - return self._scope_field - - @property - def node(self): - return self._node - - def _check_scope( - self, - requested_fixturedef: FixtureDef[object], - requested_scope: Scope, - ) -> None: - if self._scope > requested_scope: - # Try to report something helpful. - argname = requested_fixturedef.argname - fixture_stack = "\n".join( - self._format_fixturedef_line(fixturedef) - for fixturedef in self._get_fixturestack() - ) - requested_fixture = self._format_fixturedef_line(requested_fixturedef) - fail( - f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " - f"fixture {argname} with a {self._scope.value} scoped request object. " - f"Requesting fixture stack:\n{fixture_stack}\n" - f"Requested fixture:\n{requested_fixture}", - pytrace=False, - ) - - def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str: - factory = fixturedef.func - path, lineno = getfslineno(factory) - if isinstance(path, Path): - path = bestrelpath(self._pyfuncitem.session.path, path) - sig = signature(factory) - return f"{path}:{lineno + 1}: def {factory.__name__}{sig}" - - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - self._fixturedef.addfinalizer(finalizer) - - -@final -class FixtureLookupError(LookupError): - """Could not return a requested fixture (missing or invalid).""" - - def __init__( - self, argname: str | None, request: FixtureRequest, msg: str | None = None - ) -> None: - self.argname = argname - self.request = request - self.fixturestack = request._get_fixturestack() - self.msg = msg - - def formatrepr(self) -> FixtureLookupErrorRepr: - tblines: list[str] = [] - addline = tblines.append - stack = [self.request._pyfuncitem.obj] - stack.extend(map(lambda x: x.func, self.fixturestack)) - msg = self.msg - if msg is not None and len(stack) > 1: - # The last fixture raise an error, let's present - # it at the requesting side. - stack = stack[:-1] - for function in stack: - fspath, lineno = getfslineno(function) - try: - lines, _ = inspect.getsourcelines(get_real_func(function)) - except (OSError, IndexError, TypeError): - error_msg = "file %s, line %s: source code not available" - addline(error_msg % (fspath, lineno + 1)) - else: - addline(f"file {fspath}, line {lineno + 1}") - for i, line in enumerate(lines): - line = line.rstrip() - addline(" " + line) - if line.lstrip().startswith("def"): - break - - if msg is None: - fm = self.request._fixturemanager - available = set() - parent = self.request._pyfuncitem.parent - assert parent is not None - for name, fixturedefs in fm._arg2fixturedefs.items(): - faclist = list(fm._matchfactories(fixturedefs, parent)) - if faclist: - available.add(name) - if self.argname in available: - msg = ( - f" recursive dependency involving fixture '{self.argname}' detected" - ) - else: - msg = f"fixture '{self.argname}' not found" - msg += "\n available fixtures: {}".format(", ".join(sorted(available))) - msg += "\n use 'pytest --fixtures [testpath]' for help on them." - - return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) - - -class FixtureLookupErrorRepr(TerminalRepr): - def __init__( - self, - filename: str | os.PathLike[str], - firstlineno: int, - tblines: Sequence[str], - errorstring: str, - argname: str | None, - ) -> None: - self.tblines = tblines - self.errorstring = errorstring - self.filename = filename - self.firstlineno = firstlineno - self.argname = argname - - def toterminal(self, tw: TerminalWriter) -> None: - # tw.line("FixtureLookupError: %s" %(self.argname), red=True) - for tbline in self.tblines: - tw.line(tbline.rstrip()) - lines = self.errorstring.split("\n") - if lines: - tw.line( - f"{ExceptionInfoFormatter.fail_marker} {lines[0].strip()}", - red=True, - ) - for line in lines[1:]: - tw.line( - f"{ExceptionInfoFormatter.flow_marker} {line.strip()}", - red=True, - ) - tw.line() - tw.line(f"{os.fspath(self.filename)}:{self.firstlineno + 1}") - - -def call_fixture_func( - fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs -) -> FixtureValue: - if inspect.isgeneratorfunction(fixturefunc): - fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc) - generator = fixturefunc(**kwargs) - try: - fixture_result = next(generator) - except StopIteration: - raise ValueError(f"{request.fixturename} did not yield a value") from None - finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator) - request.addfinalizer(finalizer) - else: - fixturefunc = cast(Callable[..., FixtureValue], fixturefunc) - fixture_result = fixturefunc(**kwargs) - return fixture_result - - -def _teardown_yield_fixture(fixturefunc, it) -> None: - """Execute the teardown of a fixture function by advancing the iterator - after the yield and ensure the iteration ends (if not it means there is - more than one yield in the function).""" - try: - next(it) - except StopIteration: - pass - else: - fs, lineno = getfslineno(fixturefunc) - fail( - f"fixture function has more than one 'yield':\n\n" - f"{Source(fixturefunc).indent()}\n" - f"{fs}:{lineno + 1}", - pytrace=False, - ) - - -def _eval_scope_callable( - scope_callable: Callable[[str, Config], ScopeName], - fixture_name: str, - config: Config, -) -> ScopeName: - try: - # Type ignored because there is no typing mechanism to specify - # keyword arguments, currently. - result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] - except Exception as e: - raise TypeError( - f"Error evaluating {scope_callable} while defining fixture '{fixture_name}'.\n" - "Expected a function with the signature (*, fixture_name, config)" - ) from e - if not isinstance(result, str): - fail( - f"Expected {scope_callable} to return a 'str' while defining fixture '{fixture_name}', but it returned:\n" - f"{result!r}", - pytrace=False, - ) - return result - - -class FixtureDef(Generic[FixtureValue]): - """A container for a fixture definition. - - Note: At this time, only explicitly documented fields and methods are - considered public stable API. - """ - - def __init__( - self, - config: Config, - baseid: str | None | NotSetType, - argname: str, - func: _FixtureFunc[FixtureValue], - scope: Scope | ScopeName | Callable[[str, Config], ScopeName] | None, - params: Sequence[object] | None, - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, - *, - node: nodes.Node | NotSetType = NOTSET, - # only used in a deprecationwarning msg, can be removed in pytest9 - _autouse: bool = False, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - # Emit deprecation warning if deprecated baseid string is used. - if node is NOTSET: - warnings.warn(FIXTURE_BASEID_DEPRECATED, stacklevel=2) - if baseid is NOTSET: - baseid = None - # The node where this fixture was defined, if available. - # Used for node-based matching which is more robust than string matching. - self.node: Final = node if node is not NOTSET else None - # The "base" node ID for the fixture. - # - # This is a node ID prefix. A fixture is only available to a node (e.g. - # a `Function` item) if the fixture's baseid is a nodeid of a parent of - # node. - # - # For a fixture found in a Collector's object (e.g. a `Module`s module, - # a `Class`'s class), the baseid is the Collector's nodeid. - # - # For a fixture found in a conftest plugin, the baseid is the conftest's - # directory path relative to the rootdir. - # - # For other plugins, the baseid is the empty string (always matches). - # When node is available, baseid is derived from node.nodeid. - # - # Deprecated: replaced by ``node``. - self.baseid: Final = node.nodeid if node is not NOTSET else (baseid or "") - # Whether the fixture was found from a node or a conftest in the - # collection tree. Will be false for fixtures defined in non-conftest - # plugins. - # - # Deprecated: kept only to back the deprecated ``has_location`` property. - self._has_location: Final = node is not NOTSET or baseid is not None - # The fixture factory function. - self.func: Final = func - # The name by which the fixture may be requested. - self.argname: Final = argname - if scope is None: - scope = Scope.Function - elif callable(scope): - scope = _eval_scope_callable(scope, argname, config) - if isinstance(scope, str): - scope = Scope.from_user( - scope, descr=f"Fixture '{func.__name__}'", where=self.baseid - ) - self._scope: Final = scope - # If the fixture is directly parametrized, the parameter values. - self.params: Final = params - # If the fixture is directly parametrized, a tuple of explicit IDs to - # assign to the parameter values, or a callable to generate an ID given - # a parameter value. - self.ids: Final = ids - # The names requested by the fixtures. - self.argnames: Final = getfuncargnames(func, name=argname) - # If the fixture was executed, the current value of the fixture. - # Can change if the fixture is executed with different parameters. - self.cached_result: _FixtureCachedResult[FixtureValue] | None = None - self._finalizers: Final[list[Callable[[], object]]] = [] - - # only used to emit a deprecationwarning, can be removed in pytest9 - self._autouse = _autouse - - @property - def scope(self) -> ScopeName: - """Scope string, one of "function", "class", "module", "package", "session".""" - return self._scope.value - - @property - def has_location(self) -> bool: - warnings.warn(FIXTUREDEF_HAS_LOCATION_DEPRECATED, stacklevel=2) - return self._has_location - - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - self._finalizers.append(finalizer) - - def finish(self, request: SubRequest) -> None: - if self.cached_result is None: - # Already finished. It is assumed that finalizers cannot be added in - # this state. - return - - exceptions: list[BaseException] = [] - while self._finalizers: - fin = self._finalizers.pop() - try: - fin() - except BaseException as e: - exceptions.append(e) - node = request.node - # Even if finalization fails, we invalidate the cached fixture - # value and remove all finalizers because they may be bound methods - # which will keep instances alive. - self.cached_result = None - self._finalizers.clear() - if len(exceptions) == 1: - raise exceptions[0] - elif len(exceptions) > 1: - msg = f'errors while tearing down fixture "{self.argname}" of {node}' - raise BaseExceptionGroup(msg, exceptions[::-1]) - - def execute(self, request: SubRequest) -> FixtureValue: - """Return the value of this fixture, executing it if not cached.""" - # Ensure that the dependent fixtures requested by this fixture are loaded. - # This needs to be done before checking if we have a cached value, since - # if a dependent fixture has their cache invalidated, e.g. due to - # parametrization, they finalize themselves and fixtures depending on it - # (which will likely include this fixture) setting `self.cached_result = None`. - # See #4871 - requested_fixtures_that_should_finalize_us = [] - for argname in self.argnames: - fixturedef = request._get_active_fixturedef(argname) - # Saves requested fixtures in a list so we later can add our finalizer - # to them, ensuring that if a requested fixture gets torn down we get torn - # down first. This is generally handled by SetupState, but still currently - # needed when this fixture is not parametrized but depends on a parametrized - # fixture. - requested_fixtures_that_should_finalize_us.append(fixturedef) - - # Check for (and return) cached value/exception. - if self.cached_result is not None: - request_cache_key = self.cache_key(request) - cache_key = self.cached_result[1] - try: - # Attempt to make a normal == check: this might fail for objects - # which do not implement the standard comparison (like numpy arrays -- #6497). - cache_hit = bool(request_cache_key == cache_key) - except (ValueError, RuntimeError): - # If the comparison raises, use 'is' as fallback. - cache_hit = request_cache_key is cache_key - - if cache_hit: - if self.cached_result[2] is not None: - exc, exc_tb = self.cached_result[2] - raise exc.with_traceback(exc_tb) - else: - return self.cached_result[0] - # We have a previous but differently parametrized fixture instance - # so we need to tear it down before creating a new one. - self.finish(request) - assert self.cached_result is None - - # Add finalizer to requested fixtures we saved previously. - # We make sure to do this after checking for cached value to avoid - # adding our finalizer multiple times. (#12135) - finalizer = functools.partial(self.finish, request=request) - for parent_fixture in requested_fixtures_that_should_finalize_us: - parent_fixture.addfinalizer(finalizer) - - # Register the pytest_fixture_post_finalizer as the first finalizer, - # which is executed last. - assert not self._finalizers - self.addfinalizer( - lambda: request.node.ihook.pytest_fixture_post_finalizer( - fixturedef=self, request=request - ) - ) - - ihook = request.node.ihook - try: - # Setup the fixture, run the code in it, and cache the value - # in self.cached_result. - result: FixtureValue = ihook.pytest_fixture_setup( - fixturedef=self, request=request - ) - finally: - # Schedule our finalizer, even if the setup failed. - request.node.addfinalizer(finalizer) - - return result - - def cache_key(self, request: SubRequest) -> object: - return getattr(request, "param", None) - - def __repr__(self) -> str: - return f"" - - -class RequestFixtureDef(FixtureDef[FixtureRequest]): - """A custom FixtureDef for the special "request" fixture. - - A new one is generated on-demand whenever "request" is requested. - """ - - def __init__(self, request: FixtureRequest) -> None: - super().__init__( - config=request.config, - baseid=NOTSET, - argname="request", - func=lambda: request, - scope=Scope.Function, - params=None, - node=request.node, - _ispytest=True, - ) - self.cached_result = (request, [0], None) - - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - pass - - -def resolve_fixture_function( - fixturedef: FixtureDef[FixtureValue], request: FixtureRequest -) -> _FixtureFunc[FixtureValue]: - """Get the actual callable that can be called to obtain the fixture - value.""" - fixturefunc = fixturedef.func - # The fixture function needs to be bound to the actual - # request.instance so that code working with "fixturedef" behaves - # as expected. - instance = request.instance - - if fixturedef._scope is Scope.Class: - # Check if fixture is an instance method (bound to instance, not class) - if hasattr(fixturefunc, "__self__"): - bound_to = fixturefunc.__self__ - # classmethod: bound_to is the class itself (a type) - # instance method: bound_to is an instance (not a type) - if not isinstance(bound_to, type): - warnings.warn(CLASS_FIXTURE_INSTANCE_METHOD, stacklevel=2) - - if instance is not None: - # Handle the case where fixture is defined not in a test class, but some other class - # (for example a plugin class with a fixture), see #2270. - if hasattr(fixturefunc, "__self__") and not isinstance( - instance, - fixturefunc.__self__.__class__, - ): - return fixturefunc - fixturefunc = getimfunc(fixturedef.func) - if fixturefunc != fixturedef.func: - fixturefunc = fixturefunc.__get__(instance) - return fixturefunc - - -def pytest_fixture_setup( - fixturedef: FixtureDef[FixtureValue], request: SubRequest -) -> FixtureValue: - """Execution of fixture setup.""" - kwargs = {} - for argname in fixturedef.argnames: - kwargs[argname] = request.getfixturevalue(argname) - - fixturefunc = resolve_fixture_function(fixturedef, request) - my_cache_key = fixturedef.cache_key(request) - - if inspect.isasyncgenfunction(fixturefunc) or inspect.iscoroutinefunction( - fixturefunc - ): - auto_str = " with autouse=True" if fixturedef._autouse else "" - fail( - f"{request.node.name!r} requested an async fixture {request.fixturename!r}{auto_str}, " - "with no plugin or hook that handled it. This is an error, as pytest does not natively support it.\n" - "See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture", - pytrace=False, - ) - - try: - result = call_fixture_func(fixturefunc, request, kwargs) - except TEST_OUTCOME as e: - if isinstance(e, skip.Exception): - # The test requested a fixture which caused a skip. - # Don't show the fixture as the skip location, as then the user - # wouldn't know which test skipped. - e._use_item_location = True - fixturedef.cached_result = (None, my_cache_key, (e, e.__traceback__)) - raise - fixturedef.cached_result = (result, my_cache_key, None) - return result - - -@final -@dataclasses.dataclass(frozen=True) -class FixtureFunctionMarker: - scope: ScopeName | Callable[[str, Config], ScopeName] - params: tuple[object, ...] | None - autouse: bool = False - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None - name: str | None = None - - _ispytest: dataclasses.InitVar[bool] = False - - def __post_init__(self, _ispytest: bool) -> None: - check_ispytest(_ispytest) - - def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition: - if inspect.isclass(function): - raise ValueError("class fixtures not supported (maybe in the future)") - - if isinstance(function, FixtureFunctionDefinition): - raise ValueError( - f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}" - ) - - if hasattr(function, "pytestmark"): - fail( - "Marks cannot be applied to fixtures.\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function" - ) - - fixture_definition = FixtureFunctionDefinition( - function=function, fixture_function_marker=self, _ispytest=True - ) - - name = self.name or function.__name__ - if name == "request": - location = getlocation(function) - fail( - f"'request' is a reserved word for fixtures, use another name:\n {location}", - pytrace=False, - ) - - return fixture_definition - - -# TODO: paramspec/return type annotation tracking and storing -class FixtureFunctionDefinition: - def __init__( - self, - *, - function: Callable[..., Any], - fixture_function_marker: FixtureFunctionMarker, - instance: object | None = None, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self.name = fixture_function_marker.name or function.__name__ - # In order to show the function that this fixture contains in messages. - # Set the __name__ to be same as the function __name__ or the given fixture name. - self.__name__ = self.name - self._fixture_function_marker = fixture_function_marker - if instance is not None: - self._fixture_function = cast( - Callable[..., Any], function.__get__(instance) - ) - else: - self._fixture_function = function - functools.update_wrapper(self, function) - - def __repr__(self) -> str: - return f"" - - def __get__(self, instance, owner=None): - """Behave like a method if the function it was applied to was a method.""" - return FixtureFunctionDefinition( - function=self._fixture_function, - fixture_function_marker=self._fixture_function_marker, - instance=instance, - _ispytest=True, - ) - - def __call__(self, *args: Any, **kwds: Any) -> Any: - message = ( - f'Fixture "{self.name}" called directly. Fixtures are not meant to be called directly,\n' - "but are created automatically when test functions request them as parameters.\n" - "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n" - "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly" - ) - fail(message, pytrace=False) - - def _get_wrapped_function(self) -> Callable[..., Any]: - return self._fixture_function - - -@overload -def fixture( - fixture_function: Callable[..., object], - *, - scope: ScopeName | Callable[[str, Config], ScopeName] = ..., - params: Iterable[object] | None = ..., - autouse: bool = ..., - ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., - name: str | None = ..., -) -> FixtureFunctionDefinition: ... - - -@overload -def fixture( - fixture_function: None = ..., - *, - scope: ScopeName | Callable[[str, Config], ScopeName] = ..., - params: Iterable[object] | None = ..., - autouse: bool = ..., - ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., - name: str | None = None, -) -> FixtureFunctionMarker: ... - - -def fixture( - fixture_function: FixtureFunction | None = None, - *, - scope: ScopeName | Callable[[str, Config], ScopeName] = "function", - params: Iterable[object] | None = None, - autouse: bool = False, - ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, - name: str | None = None, -) -> FixtureFunctionMarker | FixtureFunctionDefinition: - """Decorator to mark a fixture factory function. - - This decorator can be used, with or without parameters, to define a - fixture function. - - The name of the fixture function can later be referenced to cause its - invocation ahead of running tests: test modules or classes can use the - ``pytest.mark.usefixtures(fixturename)`` marker. - - Test functions can directly use fixture names as input arguments in which - case the fixture instance returned from the fixture function will be - injected. - - Fixtures can provide their values to test functions using ``return`` or - ``yield`` statements. When using ``yield`` the code block after the - ``yield`` statement is executed as teardown code regardless of the test - outcome, and must yield exactly once. - - :param scope: - The scope for which this fixture is shared; one of ``"function"`` - (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``. - - This parameter may also be a callable which receives ``(fixture_name, config)`` - as parameters, and must return a ``str`` with one of the values mentioned above. - - See :ref:`dynamic scope` in the docs for more information. - - :param params: - An optional list of parameters which will cause multiple invocations - of the fixture function and all of the tests using it. The current - parameter is available in ``request.param``. - - :param autouse: - If True, the fixture func is activated for all tests that can see it. - If False (the default), an explicit reference is needed to activate - the fixture. - - :param ids: - Sequence of ids each corresponding to the params so that they are - part of the test id. If no ids are provided they will be generated - automatically from the params. - - :param name: - The name of the fixture. This defaults to the name of the decorated - function. If a fixture is used in the same module in which it is - defined, the function name of the fixture will be shadowed by the - function arg that requests the fixture; one way to resolve this is to - name the decorated function ``fixture_`` and then use - ``@pytest.fixture(name='')``. - """ - fixture_marker = FixtureFunctionMarker( - scope=scope, - params=tuple(params) if params is not None else None, - autouse=autouse, - ids=None if ids is None else ids if callable(ids) else tuple(ids), - name=name, - _ispytest=True, - ) - - # Direct decoration. - if fixture_function: - return fixture_marker(fixture_function) - - return fixture_marker - - -@deprecated( - "@pytest.yield_fixture is deprecated. Use @pytest.fixture instead; they are the same.", - category=None, # We have our own runtime warning logic -) -def yield_fixture( - fixture_function=None, - *args, - scope="function", - params=None, - autouse=False, - ids=None, - name=None, -): - """(Return a) decorator to mark a yield-fixture factory function. - - .. deprecated:: 3.0 - Use :py:func:`pytest.fixture` directly instead. - """ - warnings.warn(YIELD_FIXTURE, stacklevel=2) - return fixture( - fixture_function, - *args, - scope=scope, - params=params, - autouse=autouse, - ids=ids, - name=name, - ) - - -@fixture(scope="session") -def pytestconfig(request: FixtureRequest) -> Config: - """Session-scoped fixture that returns the session's :class:`pytest.Config` - object. - - Example:: - - def test_foo(pytestconfig): - if pytestconfig.get_verbosity() > 0: - ... - - """ - return request.config - - -def pytest_addoption(parser: Parser) -> None: - parser.addini( - "usefixtures", - type="args", - default=[], - help="List of default fixtures to be used with this project", - ) - group = parser.getgroup("general") - group.addoption( - "--fixtures", - "--funcargs", - action="store_true", - dest="showfixtures", - default=False, - help="Show available fixtures, sorted by plugin appearance " - "(fixtures with leading '_' are only shown with '-v')", - ) - group.addoption( - "--fixtures-per-test", - action="store_true", - dest="show_fixtures_per_test", - default=False, - help="Show fixtures per test", - ) - - -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - if config.option.showfixtures: - showfixtures(config) - return 0 - if config.option.show_fixtures_per_test: - show_fixtures_per_test(config) - return 0 - return None - - -def _resolve_args_directness( - argnames: Sequence[str], - indirect: bool | Sequence[str], - nodeid: str, -) -> dict[str, Literal["indirect", "direct"]]: - """Resolve if each parametrized argument must be considered an indirect - parameter to a fixture of the same name, or a direct parameter to the - parametrized function, based on the ``indirect`` parameter of the - parametrize() call. - - :param argnames: - List of argument names passed to ``parametrize()``. - :param indirect: - Same as the ``indirect`` parameter of ``parametrize()``. - :param nodeid: - Node ID to which the parametrization is applied. - :returns: - A dict mapping each arg name to either "indirect" or "direct". - """ - arg_directness: dict[str, Literal["indirect", "direct"]] - if isinstance(indirect, bool): - arg_directness = dict.fromkeys(argnames, "indirect" if indirect else "direct") - elif isinstance(indirect, Sequence): - arg_directness = dict.fromkeys(argnames, "direct") - for arg in indirect: - if arg not in argnames: - fail( - f"In {nodeid}: indirect fixture '{arg}' doesn't exist", - pytrace=False, - ) - arg_directness[arg] = "indirect" - else: - fail( - f"In {nodeid}: expected Sequence or boolean for indirect, got {type(indirect).__name__}", - pytrace=False, - ) - return arg_directness - - -def _get_direct_parametrize_args(node: nodes.Node) -> set[str]: - """Return all direct parametrization arguments of a node, so we don't - mistake them for fixtures. - - Check https://github.com/pytest-dev/pytest/issues/5036. - - These things are done later as well when dealing with parametrization - so this could be improved. - """ - parametrize_argnames: set[str] = set() - for marker in node.iter_markers(name="parametrize"): - indirect = marker.kwargs.get("indirect", False) - p_argnames, _ = ParameterSet._parse_parametrize_args( - *marker.args, **marker.kwargs - ) - p_directness = _resolve_args_directness(p_argnames, indirect, node.nodeid) - parametrize_argnames.update( - argname - for argname, directness in p_directness.items() - if directness == "direct" - ) - return parametrize_argnames - - -def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]: - """De-duplicate the sequence of names while keeping the original order.""" - # Ideally we would use a set, but it does not preserve insertion order. - return tuple(dict.fromkeys(name for seq in seqs for name in seq)) - - -class FixtureManager: - """pytest fixture definitions and information is stored and managed - from this class. - - During collection fm.parsefactories() is called multiple times to parse - fixture function definitions into FixtureDef objects and internal - data structures. - - During collection of test functions, metafunc-mechanics instantiate - a FuncFixtureInfo object which is cached per node/func-name. - This FuncFixtureInfo object is later retrieved by Function nodes - which themselves offer a fixturenames attribute. - - The FuncFixtureInfo object holds information about fixtures and FixtureDefs - relevant for a particular function. An initial list of fixtures is - assembled like this: - - - config-defined usefixtures - - autouse-marked fixtures along the collection chain up from the function - - usefixtures markers at module/class/function level - - test function funcargs - - Subsequently the funcfixtureinfo.fixturenames attribute is computed - as the closure of the fixtures needed to setup the initial fixtures, - i.e. fixtures needed by fixture functions themselves are appended - to the fixturenames list. - - Upon the test-setup phases all fixturenames are instantiated, retrieved - by a lookup of their FuncFixtureInfo. - """ - - def __init__(self, session: Session) -> None: - self.session = session - self.config: Config = session.config - # Maps a fixture name (argname) to all of the FixtureDefs in the test - # suite/plugins defined with this name. Populated by parsefactories(). - # TODO: The order of the FixtureDefs list of each arg is significant, - # explain. - self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {} - # A mapping from a node to a list of autouse fixture names it defines. - # The Session entry holds global usefixtures from config. - self._node_autousenames: Final[dict[nodes.Node, list[str]]] = { - session: list(self.config.getini("usefixtures")), - } - # Legacy fallback: nodeid string -> autouse names, for plugins still - # using the deprecated nodeid-based API without a node reference. - self._nodeid_autousenames: Final[dict[str, list[str]]] = {} - # Pending conftest modules waiting to be parsed when their Directory is collected. - # Maps directory path -> conftest plugin module. - self._pending_conftests: Final[dict[Path, object]] = {} - session.config.pluginmanager.register(self, "funcmanage") - # Flush initial conftests from directories above rootpath immediately. - # These will never get a Directory collector, so they need Session scope. - # This must happen here (not in pytest_make_collect_report) because - # collection may fail before Session collection starts (e.g. bad args). - self._flush_pending_conftests_to_session(session) - - def getfixtureinfo( - self, - node: nodes.Item, - func: Callable[..., object] | None, - cls: type | None, - ) -> FuncFixtureInfo: - """Calculate the :class:`FuncFixtureInfo` for an item. - - If ``func`` is None, or if the item sets an attribute - ``nofuncargs = True``, then ``func`` is not examined at all. - - :param node: - The item requesting the fixtures. - :param func: - The item's function. - :param cls: - If the function is a method, the method's class. - """ - if func is not None and not getattr(node, "nofuncargs", False): - argnames = getfuncargnames(func, name=node.name, cls=cls) - else: - argnames = () - usefixturesnames = self._getusefixturesnames(node) - autousenames = self._getautousenames(node) - initialnames = deduplicate_names(autousenames, usefixturesnames, argnames) - - direct_parametrize_args = _get_direct_parametrize_args(node) - - names_closure, arg2fixturedefs = self.getfixtureclosure( - parentnode=node, - initialnames=initialnames, - ignore_args=direct_parametrize_args, - ) - - return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) - - def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None: - # Fixtures defined in conftest plugins are only visible to within the - # conftest's directory. This is unlike fixtures in non-conftest plugins - # which have global visibility. Conftest fixtures are deferred until - # their Directory is collected, so we can use the Directory's nodeid. - if plugin_name and plugin_name.endswith("conftest.py"): - # Note: we explicitly do *not* use `plugin.__file__` here -- The - # difference is that plugin_name has the correct capitalization on - # case-insensitive systems (Windows) and other normalization issues - # (issue #11816). - conftestpath = absolutepath(plugin_name) - conftest_dir = conftestpath.parent - # Store conftest for deferred parsing when its Directory is collected. - self._pending_conftests[conftest_dir] = plugin - else: - # Non-conftest plugins have global visibility. - self.parsefactories(holder=plugin, node=self.session) - - @hookimpl(wrapper=True) - def pytest_make_collect_report( - self, collector: nodes.Collector - ) -> Generator[None, CollectReport, CollectReport]: - result = yield - if isinstance(collector, nodes.Directory): - plugin = self._pending_conftests.pop(collector.path, None) - if plugin is not None: - self.parsefactories(holder=plugin, node=collector) - return result - - def _flush_pending_conftests_to_session(self, session: Session) -> None: - """Assign Session scope to initial conftests whose directories won't - be collected as Directory nodes (e.g. ancestors above rootdir).""" - rootpath = session.config.rootpath - orphaned: list[tuple[Path, object]] = [] - for conftest_dir, plugin in list(self._pending_conftests.items()): - # If the conftest dir is not under rootpath, it will never get - # a Directory collector — assign it to Session now. - try: - conftest_dir.relative_to(rootpath) - except ValueError: - orphaned.append((conftest_dir, plugin)) - for conftest_dir, plugin in orphaned: - del self._pending_conftests[conftest_dir] - self.parsefactories(holder=plugin, node=session) - - def pytest_collection_finish(self) -> None: - """Clean up any conftests that were never collected by a Directory. - - After __init__ flushes above-rootdir conftests and collection pops - under-rootdir ones, remaining entries mean collection was interrupted - (e.g. UsageError for a bad path). These conftests' fixtures aren't - needed since their directories' tests weren't collected either. - """ - self._pending_conftests.clear() - - def _getautousenames(self, node: nodes.Node) -> Iterator[str]: - """Return the names of autouse fixtures applicable to node.""" - for parentnode in node.listchain(): - basenames = self._node_autousenames.get(parentnode) - if basenames: - yield from basenames - # Legacy fallback: check string-based nodeid autouse names. - nodeid_basenames = self._nodeid_autousenames.get(parentnode.nodeid) - if nodeid_basenames: - yield from nodeid_basenames - - def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: - """Return the names of usefixtures fixtures applicable to node.""" - for marker_node, mark in node.iter_markers_with_node(name="usefixtures"): - if not mark.args: - marker_node.warn( - PytestWarning( - f"usefixtures() in {node.nodeid} without arguments has no effect" - ) - ) - yield from mark.args - - def getfixtureclosure( - self, - parentnode: nodes.Node, - initialnames: tuple[str, ...], - ignore_args: AbstractSet[str], - ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]: - # Collect the closure of all fixtures, starting with the given - # fixturenames as the initial set. As we have to visit all - # factory definitions anyway, we also return an arg2fixturedefs - # mapping so that the caller can reuse it and does not have - # to re-discover fixturedefs again for each fixturename - # (discovering matching fixtures for a given name/node is expensive). - - arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} - - def getfixturedefs(argname: str) -> Sequence[FixtureDef[Any]] | None: - if argname in ignore_args: - return None - - fixturedefs = arg2fixturedefs.get(argname) - if not fixturedefs: - fixturedefs = self.getfixturedefs(argname, parentnode) - if not fixturedefs: - # Fixture not defined or not visible (will error during runtest). - return None - arg2fixturedefs[argname] = fixturedefs - return fixturedefs - - def sort_by_scope(arg_name: str) -> Scope: - try: - fixturedefs = arg2fixturedefs[arg_name] - except KeyError: - return Scope.Function - else: - return fixturedefs[-1]._scope - - fixturenames_closure = sorted( - traverse_fixture_closure( - initialnames, - getfixturedefs=getfixturedefs, - ), - key=sort_by_scope, - reverse=True, - ) - - return fixturenames_closure, arg2fixturedefs - - def pytest_generate_tests(self, metafunc: Metafunc) -> None: - """Generate new tests based on parametrized fixtures used by the given metafunc""" - - def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: - args, _ = ParameterSet._parse_parametrize_args(*mark.args, **mark.kwargs) - return args - - for argname in metafunc.fixturenames: - # Get the FixtureDefs for the argname. - fixture_defs = metafunc._arg2fixturedefs.get(argname, ()) - - # If the test itself parametrizes using this argname, give it - # precedence. - if any( - argname in get_parametrize_mark_argnames(mark) - for mark in metafunc.definition.iter_markers("parametrize") - ): - continue - - # In the common case we only look at the fixture def with the - # closest scope (last in the list). But if the fixture overrides - # another fixture, while requesting the super fixture, keep going - # in case the super fixture is parametrized (#1953). - for fixturedef in reversed(fixture_defs): - # Fixture is parametrized, apply it and stop. - if fixturedef.params is not None: - metafunc.parametrize( - argname, - fixturedef.params, - indirect=True, - scope=fixturedef.scope, - ids=fixturedef.ids, - ) - break - - # Not requesting the overridden super fixture, stop. - # - # TODO: Handle the case where the super-fixture is transitively - # requested (see #7737 and the xfail'd test - # test_override_parametrized_fixture_via_transitive_fixture). - if argname not in fixturedef.argnames: - break - - # Try next super fixture, if any. - - def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None: - # Separate parametrized setups. - items[:] = reorder_items(items) - - def _register_fixture( - self, - *, - name: str, - func: _FixtureFunc[object], - nodeid: str | None | NotSetType = NOTSET, - scope: Scope | ScopeName | Callable[[str, Config], ScopeName] = "function", - params: Sequence[object] | None = None, - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, - autouse: bool = False, - node: nodes.Node | NotSetType = NOTSET, - ) -> None: - """Register a fixture - - :param name: - The fixture's name. - :param func: - The fixture's implementation function. - :param nodeid: - The visibility of the fixture (deprecated, use node instead). - The fixture will be available to the node with this nodeid and - its children in the collection tree. None means global visibility. - :param node: - The node where the fixture is defined (preferred over nodeid). - When provided, enables node-based matching which is more robust. - :param scope: - The fixture's scope. - :param params: - The fixture's parametrization params. - :param ids: - The fixture's IDs. - :param autouse: - Whether this is an autouse fixture. - """ - # Emit deprecation warning if nodeid string. - if nodeid is not NOTSET or node is NOTSET: - warnings.warn(FIXTURE_NODEID_DEPRECATED, stacklevel=2) - fixture_def = FixtureDef( - config=self.config, - baseid=nodeid, - argname=name, - func=func, - scope=scope, - params=params, - ids=ids, - _ispytest=True, - _autouse=autouse, - node=node, - ) - - faclist = self._arg2fixturedefs.setdefault(name, []) - # Insert the fixturedef into the list while maintaining a partial order - # based on visibility: a fixturedef whose visibility is more specific - # sorts after a more general one, so that it takes precedence in the - # override chain (the last applicable fixturedef in the list is used - # first, see getfixturedefs). - # fixturedefs with the same visibility keep registration order, i.e. the - # last registered wins. - # The order between non-comparable fixturedefs doesn't matter since they - # cannot be visible together. - # The idea is that a fixture that is defined closer to the item should - # take precedence. - for i, existing in enumerate(faclist): - if is_visibility_more_specific(existing, fixture_def): - faclist.insert(i, fixture_def) - break - else: - faclist.append(fixture_def) - if autouse: - if node is not NOTSET: - self._node_autousenames.setdefault(node, []).append(name) - elif nodeid is not NOTSET and nodeid is not None: - # Legacy: plugin passed nodeid string without node reference. - self._nodeid_autousenames.setdefault(nodeid, []).append(name) - else: - # Global plugin autouse fixtures go under Session. - self._node_autousenames.setdefault(self.session, []).append(name) - - @overload - def parsefactories( - self, - node_or_obj: nodes.Node, - ) -> None: - raise NotImplementedError() - - @overload - @deprecated( - "parsefactories(obj, nodeid) is deprecated, use parsefactories(holder=obj, node=node) instead" - ) - def parsefactories( - self, - node_or_obj: object, - nodeid: str | None, - ) -> None: - raise NotImplementedError() - - @overload - def parsefactories( - self, - node_or_obj: NotSetType = ..., - nodeid: NotSetType = ..., - *, - holder: object, - node: nodes.Node, - ) -> None: - raise NotImplementedError() - - def parsefactories( - self, - node_or_obj: nodes.Node | object | NotSetType = NOTSET, - nodeid: str | None | NotSetType = NOTSET, - *, - holder: object | NotSetType = NOTSET, - node: nodes.Node | NotSetType = NOTSET, - ) -> None: - """Collect fixtures from a collection node or object. - - Found fixtures are parsed into `FixtureDef`s and saved. - - The preferred API uses keyword-only arguments: - - ``holder``: The object to scan for fixtures. - - ``node``: The node determining fixture visibility. - - Legacy positional API (translated internally): - - ``parsefactories(node)``: Uses node.obj as holder, node for scope. - - ``parsefactories(obj, nodeid)``: Uses obj as holder, nodeid string for scope. - """ - # Translate legacy API to holder/node sources of truth - # Either effective_node or effective_nodeid will be set, not both - effective_node: nodes.Node | NotSetType = NOTSET - effective_nodeid: str | None | NotSetType = NOTSET - - if holder is not NOTSET: - # New API: holder and node explicitly provided - holderobj = holder - effective_node = node - elif node_or_obj is NOTSET: - raise TypeError("parsefactories() requires holder or node_or_obj") - elif nodeid is not NOTSET: - # Legacy: parsefactories(obj, nodeid) - string-based scoping only. - warnings.warn(PARSEFACTORIES_NODEID_DEPRECATED, stacklevel=2) - holderobj = node_or_obj - effective_nodeid = nodeid - else: - # parsefactories(node) - node has .obj attribute - assert isinstance(node_or_obj, nodes.Node) - holderobj = cast(object, node_or_obj.obj) # type: ignore[attr-defined] - effective_node = node_or_obj - - # Avoid accessing `@property` (and other descriptors) when iterating fixtures. - if not safe_isclass(holderobj) and not isinstance(holderobj, types.ModuleType): - holderobj_tp: object = type(holderobj) - else: - holderobj_tp = holderobj - - for name in dir(holderobj): - # The attribute can be an arbitrary descriptor, so the attribute - # access below can raise. safe_getattr() ignores such exceptions. - obj_ub = safe_getattr(holderobj_tp, name, None) - if type(obj_ub) is FixtureFunctionDefinition: - marker = obj_ub._fixture_function_marker - if marker.name: - fixture_name = marker.name - else: - fixture_name = name - - # OK we know it is a fixture -- now safe to look up on the _instance_. - try: - obj = getattr(holderobj, name) - # if the fixture is named in the decorator we cannot find it in the module - except AttributeError: - obj = obj_ub - - func = obj._get_wrapped_function() - - self._register_fixture( - name=fixture_name, - func=func, - scope=marker.scope, - params=marker.params, - ids=marker.ids, - autouse=marker.autouse, - node=effective_node, - nodeid=effective_nodeid, - ) - - def getfixturedefs( - self, argname: str, node: nodes.Node - ) -> Sequence[FixtureDef[Any]] | None: - """Get FixtureDefs for a fixture name which are applicable - to a given node. - - Returns None if there are no fixtures at all defined with the given - name. (This is different from the case in which there are fixtures - with the given name, but none applicable to the node. In this case, - an empty result is returned). - - :param argname: Name of the fixture to search for. - :param node: The requesting Node. - """ - try: - fixturedefs = self._arg2fixturedefs[argname] - except KeyError: - return None - return tuple(self._matchfactories(fixturedefs, node)) - - def _matchfactories( - self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node - ) -> Iterator[FixtureDef[Any]]: - # Collect parent nodes and their IDs for matching - parent_nodes = set(node.iter_parents()) - parentnodeids = {n.nodeid for n in parent_nodes} - - for fixturedef in fixturedefs: - if fixturedef.node is not None: - # Node-based matching: check if fixture's node is a parent - if fixturedef.node in parent_nodes: - yield fixturedef - elif fixturedef.baseid in parentnodeids: - # Fallback to string-based matching for legacy/plugins - yield fixturedef - - -def show_fixtures_per_test(config: Config) -> int | ExitCode: - from _pytest.main import wrap_session - - return wrap_session(config, _show_fixtures_per_test) - - -_PYTEST_DIR = Path(_pytest.__file__).parent - - -def _pretty_fixture_path(invocation_dir: Path, func) -> str: - loc = Path(getlocation(func, invocation_dir)) - prefix = Path("...", "_pytest") - try: - return str(prefix / loc.relative_to(_PYTEST_DIR)) - except ValueError: - return bestrelpath(invocation_dir, loc) - - -def _get_fixtures_per_test(test: nodes.Item) -> Iterator[FixtureDef[object]]: - """Returns all fixtures used by the test item except for those created by - direct parametrization and those requested dynamically with - ``request.getfixturevalue``. - - The justification for excluding fixtures created by direct parametrization - is that for users, they are internal implementation detail. - - Dynamically requested fixtures are excluded because they are not known - statically. - """ - from _pytest.python import DirectParamFixtureDef - - # Custom Items may not have _fixtureinfo attribute. - fixture_info: FuncFixtureInfo | None = getattr(test, "_fixtureinfo", None) - if fixture_info is None: - return # pragma: no cover - - # dict key not used in loop but needed for sorting. - for argname, fixturedefs in sorted(fixture_info.name2fixturedefs.items()): - if not fixturedefs: - # Not supposed to be empty, but for safety. - continue # pragma: no cover - # Last item is expected to be the one directly used by the test item. - fixturedef = fixturedefs[-1] - if isinstance(fixturedef, DirectParamFixtureDef): - continue - yield fixturedef - - -def _show_fixtures_per_test(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - invocation_dir = config.invocation_params.dir - tw = _pytest.config.create_terminal_writer(config) - verbose = config.get_verbosity() - - def get_best_relpath(func) -> str: - loc = getlocation(func, invocation_dir) - return bestrelpath(invocation_dir, Path(loc)) - - def write_fixture(fixture_def: FixtureDef[object]) -> None: - argname = fixture_def.argname - if verbose <= 0 and argname.startswith("_"): - return - prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func) - tw.write(f"{argname}", green=True) - tw.write(f" -- {prettypath}", yellow=True) - tw.write("\n") - fixture_doc = inspect.getdoc(fixture_def.func) - if fixture_doc: - write_docstring( - tw, - fixture_doc.split("\n\n", maxsplit=1)[0] - if verbose <= 0 - else fixture_doc, - ) - else: - tw.line(" no docstring available", red=True) - - def write_item(item: nodes.Item) -> None: - fixturedefs = list(_get_fixtures_per_test(item)) - if not fixturedefs: - # This test item does not use any fixtures. - return - - tw.line() - tw.sep("-", f"fixtures used by {item.name}") - # TODO: Fix this type ignore. - tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined] - - for fixturedef in fixturedefs: - write_fixture(fixturedef) - - for session_item in session.items: - write_item(session_item) - - -def showfixtures(config: Config) -> int | ExitCode: - from _pytest.main import wrap_session - - return wrap_session(config, _showfixtures_main) - - -def _showfixtures_main(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - invocation_dir = config.invocation_params.dir - tw = _pytest.config.create_terminal_writer(config) - verbose = config.get_verbosity() - - fm = session._fixturemanager - - available = [] - seen: set[tuple[str, str]] = set() - - for argname, fixturedefs in fm._arg2fixturedefs.items(): - assert fixturedefs is not None - if not fixturedefs: - continue - for fixturedef in fixturedefs: - loc = getlocation(fixturedef.func, invocation_dir) - if (fixturedef.argname, loc) in seen: - continue - seen.add((fixturedef.argname, loc)) - available.append( - ( - len(fixturedef.baseid), - fixturedef.func.__module__, - _pretty_fixture_path(invocation_dir, fixturedef.func), - fixturedef.argname, - fixturedef, - ) - ) - - available.sort() - currentmodule = None - for baseid, module, prettypath, argname, fixturedef in available: - if currentmodule != module: - if not module.startswith("_pytest."): - tw.line() - tw.sep("-", f"fixtures defined from {module}") - currentmodule = module - if verbose <= 0 and argname.startswith("_"): - continue - tw.write(f"{argname}", green=True) - if fixturedef.scope != "function": - tw.write(f" [{fixturedef.scope} scope]", cyan=True) - tw.write(f" -- {prettypath}", yellow=True) - tw.write("\n") - doc = inspect.getdoc(fixturedef.func) - if doc: - write_docstring( - tw, doc.split("\n\n", maxsplit=1)[0] if verbose <= 0 else doc - ) - else: - tw.line(" no docstring available", red=True) - tw.line() - - -def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: - for line in doc.split("\n"): - tw.line(indent + line) - - -def register_fixture( - *, - name: str, - func: _FixtureFunc[object], - node: nodes.Node, - scope: ScopeName | Callable[[str, Config], ScopeName] = "function", - params: Sequence[object] | None = None, - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, - autouse: bool = False, -) -> None: - """Register a fixture imperatively. - - This is an advanced function intended for use by plugins. - - Normally, fixtures should be registered declaratively using the - :func:`@pytest.fixture ` decorator. Pytest looks for these - fixture definitions during the collection phase and registers them - automatically. For some plugin usecases the declarative interface can be - cumbersome or nonviable, in which case the imperative interface can be used. - - Fixture registration is expected to happen during the collection phase, and - this is the only sanctioned use. However, to allow for more creative uses, - this is not enforced. But do so at your own risk! - - .. versionadded: 9.1 - - :param name: - The fixture's name. - :param func: - The fixture's implementation function. - :param node: - The visibility of the fixture. - - Only items that are descendents of this node in the collection tree will - be able to request this fixture. You can think of this as the place - where you would put the `@pytest.fixture`. - - For global visibility, pass the :class:`session ` node, - which is the root of the collection tree. - :param scope: - The fixture's scope. - :param params: - The fixture's parametrization params. - :param ids: - The fixture's IDs. - :param autouse: - Whether this is an autouse fixture. - """ - node.session._fixturemanager._register_fixture( - name=name, - func=func, - node=node, - scope=scope, - params=params, - ids=ids, - autouse=autouse, - ) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/freeze_support.py b/tests/venv2/lib/python3.11/site-packages/_pytest/freeze_support.py deleted file mode 100644 index 959ff07..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/freeze_support.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Provides a function to report all internal modules for using freezing -tools.""" - -from __future__ import annotations - -from collections.abc import Iterator -import types - - -def freeze_includes() -> list[str]: - """Return a list of module names used by pytest that should be - included by cx_freeze.""" - import _pytest - - result = list(_iter_all_modules(_pytest)) - return result - - -def _iter_all_modules( - package: str | types.ModuleType, - prefix: str = "", -) -> Iterator[str]: - """Iterate over the names of all modules that can be found in the given - package, recursively. - - >>> import _pytest - >>> list(_iter_all_modules(_pytest)) - ['_pytest._argcomplete', '_pytest._code.code', ...] - """ - import os - import pkgutil - - if isinstance(package, str): - path = package - else: - # Type ignored because typeshed doesn't define ModuleType.__path__ - # (only defined on packages). - package_path = package.__path__ - path, prefix = package_path[0], package.__name__ + "." - for _, name, is_package in pkgutil.iter_modules([path]): - if is_package: - for m in _iter_all_modules(os.path.join(path, name), prefix=name + "."): - yield prefix + m - else: - yield prefix + name diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/helpconfig.py b/tests/venv2/lib/python3.11/site-packages/_pytest/helpconfig.py deleted file mode 100644 index fdba02b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/helpconfig.py +++ /dev/null @@ -1,293 +0,0 @@ -# mypy: allow-untyped-defs -"""Version info, help messages, tracing configuration.""" - -from __future__ import annotations - -import argparse -from collections.abc import Generator -from collections.abc import Sequence -import os -import sys -from typing import Any - -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import PrintHelp -from _pytest.config.argparsing import Parser -from _pytest.terminal import TerminalReporter -import pytest - - -class HelpAction(argparse.Action): - """An argparse Action that will raise a PrintHelp exception in order to skip - the rest of the argument parsing when --help is passed. - - This prevents argparse from raising UsageError when `--help` is used along - with missing required arguments when any are defined, for example by - ``pytest_addoption``. This is similar to the way that the builtin argparse - --help option is implemented by raising SystemExit. - - To opt in to this behavior, the parse caller must set - `namespace._raise_print_help = True`. Otherwise it just sets the option. - """ - - def __init__( - self, option_strings: Sequence[str], dest: str, *, help: str | None = None - ) -> None: - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=True, - default=False, - help=help, - ) - - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: str | Sequence[Any] | None, - option_string: str | None = None, - ) -> None: - setattr(namespace, self.dest, self.const) - - if getattr(namespace, "_raise_print_help", False): - raise PrintHelp - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("debugconfig") - group.addoption( - "-V", - "--version", - action="count", - default=0, - dest="version", - help="Display pytest version and information about plugins. " - "When given twice, also display information about plugins.", - ) - group._addoption( # private to use reserved lower-case short option - "-h", - "--help", - action=HelpAction, - dest="help", - help="Show help message and configuration info", - ) - group._addoption( # private to use reserved lower-case short option - "-p", - action="append", - dest="plugins", - default=[], - metavar="name", - help="Early-load given plugin module name or entry point (multi-allowed). " - "To avoid loading of plugins, use the `no:` prefix, e.g. " - "`no:doctest`. See also --disable-plugin-autoload.", - ) - group.addoption( - "--disable-plugin-autoload", - action="store_true", - default=False, - help="Disable plugin auto-loading through entry point packaging metadata. " - "Only plugins explicitly specified in -p or env var PYTEST_PLUGINS will be loaded.", - ) - group.addoption( - "--traceconfig", - "--trace-config", - action="store_true", - default=False, - help="Trace considerations of conftest.py files", - ) - group.addoption( - "--debug", - action="store", - nargs="?", - const="pytestdebug.log", - dest="debug", - metavar="DEBUG_FILE_NAME", - help="Store internal tracing debug information in this log file. " - "This file is opened with 'w' and truncated as a result, care advised. " - "Default: pytestdebug.log.", - ) - group._addoption( # private to use reserved lower-case short option - "-o", - "--override-ini", - dest="override_ini", - action="append", - help='Override configuration option with "option=value" style, ' - "e.g. `-o strict_xfail=True -o cache_dir=cache`.", - ) - - -@pytest.hookimpl(wrapper=True) -def pytest_cmdline_parse() -> Generator[None, Config, Config]: - config = yield - - if config.option.debug: - # --debug | --debug was provided. - path = config.option.debug - debugfile = open(path, "w", encoding="utf-8") - debugfile.write( - "versions pytest-{}, " - "python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format( - pytest.__version__, - ".".join(map(str, sys.version_info)), - config.invocation_params.dir, - os.getcwd(), - config.invocation_params.args, - ) - ) - config.trace.root.setwriter(debugfile.write) - undo_tracing = config.pluginmanager.enable_tracing() - sys.stderr.write(f"writing pytest debug information to {path}\n") - - def unset_tracing() -> None: - debugfile.close() - sys.stderr.write(f"wrote pytest debug information to {debugfile.name}\n") - config.trace.root.setwriter(None) - undo_tracing() - - config.add_cleanup(unset_tracing) - - return config - - -def show_version_verbose(config: Config) -> None: - """Show verbose pytest version installation, including plugins.""" - sys.stdout.write( - f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n" - ) - plugininfo = getpluginversioninfo(config) - if plugininfo: - for line in plugininfo: - sys.stdout.write(line + "\n") - - -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - # Note: a single `--version` argument is handled directly by `Config.main()` to avoid starting up the entire - # pytest infrastructure just to display the version (#13574). - if config.option.version > 1: - show_version_verbose(config) - return ExitCode.OK - elif config.option.help: - config._do_configure() - showhelp(config) - config._ensure_unconfigure() - return ExitCode.OK - return None - - -def showhelp(config: Config) -> None: - import textwrap - - reporter: TerminalReporter | None = config.pluginmanager.get_plugin( - "terminalreporter" - ) - assert reporter is not None - tw = reporter._tw - tw.write(config._parser.optparser.format_help()) - tw.line() - tw.line( - "[pytest] configuration options in the first " - "pytest.toml|pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" - ) - tw.line() - - columns = tw.fullwidth # costly call - indent_len = 24 # based on argparse's max_help_position=24 - indent = " " * indent_len - for name in config._parser._inidict: - help, type, _default = config._parser._inidict[name] - if help is None: - raise TypeError(f"help argument cannot be None for {name}") - spec = f"{name} ({type}):" - tw.write(f" {spec}") - spec_len = len(spec) - if spec_len > (indent_len - 3): - # Display help starting at a new line. - tw.line() - helplines = textwrap.wrap( - help, - columns, - initial_indent=indent, - subsequent_indent=indent, - break_on_hyphens=False, - ) - - for line in helplines: - tw.line(line) - else: - # Display help starting after the spec, following lines indented. - tw.write(" " * (indent_len - spec_len - 2)) - wrapped = textwrap.wrap(help, columns - indent_len, break_on_hyphens=False) - - if wrapped: - tw.line(wrapped[0]) - for line in wrapped[1:]: - tw.line(indent + line) - - tw.line() - tw.line("Environment variables:") - vars = [ - ( - "CI", - "When set to a non-empty value, pytest knows it is running in a " - "CI process and does not truncate summary info", - ), - ("BUILD_NUMBER", "Equivalent to CI"), - ("PYTEST_ADDOPTS", "Extra command line options"), - ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"), - ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"), - ("PYTEST_DEBUG", "Set to enable debug tracing of pytest's internals"), - ("PYTEST_DEBUG_TEMPROOT", "Override the system temporary directory"), - ("PYTEST_THEME", "The Pygments style to use for code output"), - ("PYTEST_THEME_MODE", "Set the PYTEST_THEME to be either 'dark' or 'light'"), - ] - for name, help in vars: - tw.line(f" {name:<24} {help}") - tw.line() - tw.line() - - tw.line("to see available markers type: pytest --markers") - tw.line("to see available fixtures type: pytest --fixtures") - tw.line( - "(shown according to specified file_or_dir or current dir " - "if not specified; fixtures with leading '_' are only shown " - "with the '-v' option" - ) - - for warningreport in reporter.stats.get("warnings", []): - tw.line("warning : " + warningreport.message, red=True) - - -def getpluginversioninfo(config: Config) -> list[str]: - lines = [] - plugininfo = config.pluginmanager.list_plugin_distinfo() - if plugininfo: - lines.append("registered third-party plugins:") - for plugin, dist in plugininfo: - loc = getattr(plugin, "__file__", repr(plugin)) - content = f"{dist.project_name}-{dist.version} at {loc}" - lines.append(" " + content) - return lines - - -def pytest_report_header(config: Config) -> list[str]: - lines = [] - if config.option.debug or config.option.traceconfig: - lines.append(f"using: pytest-{pytest.__version__}") - - verinfo = getpluginversioninfo(config) - if verinfo: - lines.extend(verinfo) - - if config.option.traceconfig: - lines.append("active plugins:") - items = config.pluginmanager.list_name_plugin() - for name, plugin in items: - if hasattr(plugin, "__file__"): - r = plugin.__file__ - else: - r = repr(plugin) - lines.append(f" {name:<20}: {r}") - return lines diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/hookspec.py b/tests/venv2/lib/python3.11/site-packages/_pytest/hookspec.py deleted file mode 100644 index 6c5dd4b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/hookspec.py +++ /dev/null @@ -1,1289 +0,0 @@ -# mypy: allow-untyped-defs -# ruff: noqa: T100 -"""Hook specifications for pytest plugins which are invoked by pytest itself -and by builtin plugins.""" - -from __future__ import annotations - -from collections.abc import Mapping -from collections.abc import Sequence -from pathlib import Path -from typing import Any -from typing import TYPE_CHECKING - -from pluggy import HookspecMarker - - -if TYPE_CHECKING: - import pdb - from typing import Literal - import warnings - - from _pytest._code.code import ExceptionInfo - from _pytest._code.code import ExceptionRepr - from _pytest.config import _PluggyPlugin - from _pytest.config import Config - from _pytest.config import ExitCode - from _pytest.config import PytestPluginManager - from _pytest.config.argparsing import Parser - from _pytest.fixtures import FixtureDef - from _pytest.fixtures import SubRequest - from _pytest.main import Session - from _pytest.nodes import Collector - from _pytest.nodes import Item - from _pytest.outcomes import Exit - from _pytest.python import Class - from _pytest.python import Function - from _pytest.python import Metafunc - from _pytest.python import Module - from _pytest.reports import CollectReport - from _pytest.reports import TestReport - from _pytest.runner import CallInfo - from _pytest.terminal import TerminalReporter - from _pytest.terminal import TestShortLogReport - - -hookspec = HookspecMarker("pytest") - -# ------------------------------------------------------------------------- -# Initialization hooks called for every plugin -# ------------------------------------------------------------------------- - - -@hookspec(historic=True) -def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: - """Called at plugin registration time to allow adding new hooks via a call to - :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. - - :param pluginmanager: The pytest plugin manager. - - .. note:: - This hook is incompatible with hook wrappers. - - Use in conftest plugins - ======================= - - If a conftest plugin implements this hook, it will be called immediately - when the conftest is registered. - """ - - -@hookspec(historic=True) -def pytest_plugin_registered( - plugin: _PluggyPlugin, - plugin_name: str, - manager: PytestPluginManager, -) -> None: - """A new pytest plugin got registered. - - :param plugin: The plugin module or instance. - :param plugin_name: The name by which the plugin is registered. - :param manager: The pytest plugin manager. - - .. note:: - This hook is incompatible with hook wrappers. - - Use in conftest plugins - ======================= - - If a conftest plugin implements this hook, it will be called immediately - when the conftest is registered, once for each plugin registered thus far - (including itself!), and for all plugins thereafter when they are - registered. - """ - - -@hookspec(historic=True) -def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: - """Register argparse-style options and config-style config values, - called once at the beginning of a test run. - - :param parser: - To add command line options, call - :py:func:`parser.addoption(...) `. - To add config-file values call :py:func:`parser.addini(...) - `. - - :param pluginmanager: - The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s - or :py:func:`~pytest.hookimpl`'s and allow one plugin to call another plugin's hooks - to change how command line options are added. - - Options can later be accessed through the - :py:class:`config ` object, respectively: - - - :py:func:`config.getoption(name) ` to - retrieve the value of a command line option. - - - :py:func:`config.getini(name) ` to retrieve - a value read from a configuration file. - - The config object is passed around on many internal objects via the ``.config`` - attribute or can be retrieved as the ``pytestconfig`` fixture. - - .. note:: - This hook is incompatible with hook wrappers. - - Use in conftest plugins - ======================= - - If a conftest plugin implements this hook, it will be called immediately - when the conftest is registered. - - This hook is only called for :ref:`initial conftests `. - """ - - -@hookspec(historic=True) -def pytest_configure(config: Config) -> None: - """Allow plugins and conftest files to perform initial configuration. - - .. note:: - This hook is incompatible with hook wrappers. - - :param config: The pytest config object. - - Use in conftest plugins - ======================= - - This hook is called for every :ref:`initial conftest ` file - after command line options have been parsed. After that, the hook is called - for other conftest files as they are registered. - """ - - -# ------------------------------------------------------------------------- -# Bootstrapping hooks called for plugins registered early enough: -# internal and 3rd party plugins. -# ------------------------------------------------------------------------- - - -@hookspec(firstresult=True) -def pytest_cmdline_parse( - pluginmanager: PytestPluginManager, args: list[str] -) -> Config | None: - """Return an initialized :class:`~pytest.Config`, parsing the specified args. - - Stops at first non-None result, see :ref:`firstresult`. - - .. note:: - This hook is only called for plugin classes passed to the - ``plugins`` arg when using `pytest.main`_ to perform an in-process - test run. - - :param pluginmanager: The pytest plugin manager. - :param args: List of arguments passed on the command line. - :returns: A pytest config object. - - Use in conftest plugins - ======================= - - This hook is not called for conftest files. - """ - - -def pytest_load_initial_conftests( - early_config: Config, parser: Parser, args: list[str] -) -> None: - """Called to implement the loading of :ref:`initial conftest files - ` ahead of command line option parsing. - - :param early_config: The pytest config object. - :param args: Arguments passed on the command line. - :param parser: To add command line options. - - Use in conftest plugins - ======================= - - This hook is not called for conftest files. - """ - - -@hookspec(firstresult=True) -def pytest_cmdline_main(config: Config) -> ExitCode | int | None: - """Called for performing the main command line action. - - The default implementation will invoke the configure hooks and - :hook:`pytest_runtestloop`. - - Stops at first non-None result, see :ref:`firstresult`. - - :param config: The pytest config object. - :returns: The exit code. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. - """ - - -# ------------------------------------------------------------------------- -# collection hooks -# ------------------------------------------------------------------------- - - -@hookspec(firstresult=True) -def pytest_collection(session: Session) -> object | None: - """Perform the collection phase for the given session. - - Stops at first non-None result, see :ref:`firstresult`. - The return value is not used, but only stops further processing. - - The default collection phase is this (see individual hooks for full details): - - 1. Starting from ``session`` as the initial collector: - - 1. ``pytest_collectstart(collector)`` - 2. ``report = pytest_make_collect_report(collector)`` - 3. ``pytest_exception_interact(collector, call, report)`` if an interactive exception occurred - 4. For each collected node: - - 1. If an item, ``pytest_itemcollected(item)`` - 2. If a collector, recurse into it. - - 5. ``pytest_collectreport(report)`` - - 2. ``pytest_collection_modifyitems(session, config, items)`` - - 1. ``pytest_deselected(items)`` for any deselected items (may be called multiple times) - - 3. Set ``session.items`` to the list of collected items - 4. ``pytest_collection_finish(session)`` - 5. Set ``session.testscollected`` to the number of collected items - - You can implement this hook to only perform some action before collection, - for example the terminal plugin uses it to start displaying the collection - counter (and returns `None`). - - :param session: The pytest session object. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. - """ - - -def pytest_collection_modifyitems( - session: Session, config: Config, items: list[Item] -) -> None: - """Called after collection has been performed. May filter or re-order - the items in-place. - - When items are deselected (filtered out from ``items``), - the hook :hook:`pytest_deselected` must be called explicitly - with the deselected items to properly notify other plugins, - e.g. with ``config.hook.pytest_deselected(items=deselected_items)``. - - :param session: The pytest session object. - :param config: The pytest config object. - :param items: List of item objects. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -def pytest_collection_finish(session: Session) -> None: - """Called after collection has been performed and modified. - - :param session: The pytest session object. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -@hookspec(firstresult=True) -def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: - """Return ``True`` to ignore this path for collection. - - Return ``None`` to let other plugins ignore the path for collection. - - Returning ``False`` will forcefully *not* ignore this path for collection, - without giving a chance for other plugins to ignore this path. - - This hook is consulted for all files and directories prior to calling - more specific hooks. - - Stops at first non-None result, see :ref:`firstresult`. - - :param collection_path: The path to analyze. - :type collection_path: pathlib.Path - :param config: The pytest config object. - - .. versionchanged:: 7.0.0 - The ``collection_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``path`` parameter. The ``path`` parameter - has been deprecated and removed in pytest 9.0.0. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collection path, only - conftest files in parent directories of the collection path are consulted - (if the path is a directory, its own conftest file is *not* consulted - a - directory cannot ignore itself!). - """ - - -@hookspec(firstresult=True) -def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: - """Create a :class:`~pytest.Collector` for the given directory, or None if - not relevant. - - .. versionadded:: 8.0 - - For best results, the returned collector should be a subclass of - :class:`~pytest.Directory`, but this is not required. - - The new node needs to have the specified ``parent`` as a parent. - - Stops at first non-None result, see :ref:`firstresult`. - - :param path: The path to analyze. - :type path: pathlib.Path - - See :ref:`custom directory collectors` for a simple example of use of this - hook. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collection path, only - conftest files in parent directories of the collection path are consulted - (if the path is a directory, its own conftest file is *not* consulted - a - directory cannot collect itself!). - """ - - -def pytest_collect_file(file_path: Path, parent: Collector) -> Collector | None: - """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. - - For best results, the returned collector should be a subclass of - :class:`~pytest.File`, but this is not required. - - The new node needs to have the specified ``parent`` as a parent. - - :param file_path: The path to analyze. - :type file_path: pathlib.Path - - .. versionchanged:: 7.0.0 - The ``file_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``path`` parameter. The ``path`` parameter - has been deprecated and removed in pytest 9.0.0. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given file path, only - conftest files in parent directories of the file path are consulted. - """ - - -# logging hooks for collection - - -def pytest_collectstart(collector: Collector) -> None: - """Collector starts collecting. - - :param collector: - The collector. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories are - consulted. - """ - - -def pytest_itemcollected(item: Item) -> None: - """We just collected a test item. - - :param item: - The item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_collectreport(report: CollectReport) -> None: - """Collector finished collecting. - - :param report: - The collect report. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories are - consulted. - """ - - -def pytest_deselected(items: Sequence[Item]) -> None: - """Called for deselected test items, e.g. by keyword. - - Note that this hook has two integration aspects for plugins: - - - it can be *implemented* to be notified of deselected items - - it must be *called* from :hook:`pytest_collection_modifyitems` - implementations when items are deselected (to properly notify other plugins). - - May be called multiple times. - - :param items: - The items. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. - """ - - -@hookspec(firstresult=True) -def pytest_make_collect_report(collector: Collector) -> CollectReport | None: - """Perform :func:`collector.collect() ` and return - a :class:`~pytest.CollectReport`. - - Stops at first non-None result, see :ref:`firstresult`. - - :param collector: - The collector. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories are - consulted. - """ - - -# ------------------------------------------------------------------------- -# Python test function related hooks -# ------------------------------------------------------------------------- - - -@hookspec(firstresult=True) -def pytest_pycollect_makemodule(module_path: Path, parent) -> Module | None: - """Return a :class:`pytest.Module` collector or None for the given path. - - This hook will be called for each matching test module path. - The :hook:`pytest_collect_file` hook needs to be used if you want to - create test modules for files that do not match as a test module. - - Stops at first non-None result, see :ref:`firstresult`. - - :param module_path: The path of the module to collect. - :type module_path: pathlib.Path - - .. versionchanged:: 7.0.0 - The ``module_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``path`` parameter. The ``path`` parameter has been - deprecated in favor of ``module_path`` and removed in pytest 9.0.0. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given parent collector, - only conftest files in the collector's directory and its parent directories - are consulted. - """ - - -@hookspec(firstresult=True) -def pytest_pycollect_makeitem( - collector: Module | Class, name: str, obj: object -) -> None | Item | Collector | list[Item | Collector]: - """Return a custom item/collector for a Python object in a module, or None. - - Stops at first non-None result, see :ref:`firstresult`. - - :param collector: - The module/class collector. - :param name: - The name of the object in the module/class. - :param obj: - The object. - :returns: - The created items/collectors. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories - are consulted. - """ - - -@hookspec(firstresult=True) -def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: - """Call underlying test function. - - Stops at first non-None result, see :ref:`firstresult`. - - :param pyfuncitem: - The function item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only - conftest files in the item's directory and its parent directories - are consulted. - """ - - -def pytest_generate_tests(metafunc: Metafunc) -> None: - """Generate (multiple) parametrized calls to a test function. - - :param metafunc: - The :class:`~pytest.Metafunc` helper for the test function. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given function definition, - only conftest files in the functions's directory and its parent directories - are consulted. - """ - - -@hookspec(firstresult=True) -def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None: - """Return a user-friendly string representation of the given ``val`` - that will be used by @pytest.mark.parametrize calls, or None if the hook - doesn't know about ``val``. - - The parameter name is available as ``argname``, if required. - - Stops at first non-None result, see :ref:`firstresult`. - - :param config: The pytest config object. - :param val: The parametrized value. - :param argname: The automatic parameter name produced by pytest. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. - """ - - -# ------------------------------------------------------------------------- -# runtest related hooks -# ------------------------------------------------------------------------- - - -@hookspec(firstresult=True) -def pytest_runtestloop(session: Session) -> object | None: - """Perform the main runtest loop (after collection finished). - - The default hook implementation performs the runtest protocol for all items - collected in the session (``session.items``), unless the collection failed - or the ``collectonly`` pytest option is set. - - If at any point :py:func:`pytest.exit` is called, the loop is - terminated immediately. - - If at any point ``session.shouldfail`` or ``session.shouldstop`` are set, the - loop is terminated after the runtest protocol for the current item is finished. - - :param session: The pytest session object. - - Stops at first non-None result, see :ref:`firstresult`. - The return value is not used, but only stops further processing. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. - """ - - -@hookspec(firstresult=True) -def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: - """Perform the runtest protocol for a single test item. - - The default runtest protocol is this (see individual hooks for full details): - - - ``pytest_runtest_logstart(nodeid, location)`` - - - Setup phase: - - ``call = pytest_runtest_setup(item)`` (wrapped in ``CallInfo(when="setup")``) - - ``report = pytest_runtest_makereport(item, call)`` - - ``pytest_runtest_logreport(report)`` - - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - - - Call phase, if the setup passed and the ``setuponly`` pytest option is not set: - - ``call = pytest_runtest_call(item)`` (wrapped in ``CallInfo(when="call")``) - - ``report = pytest_runtest_makereport(item, call)`` - - ``pytest_runtest_logreport(report)`` - - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - - - Teardown phase: - - ``call = pytest_runtest_teardown(item, nextitem)`` (wrapped in ``CallInfo(when="teardown")``) - - ``report = pytest_runtest_makereport(item, call)`` - - ``pytest_runtest_logreport(report)`` - - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - - - ``pytest_runtest_logfinish(nodeid, location)`` - - :param item: Test item for which the runtest protocol is performed. - :param nextitem: The scheduled-to-be-next test item (or None if this is the end my friend). - - Stops at first non-None result, see :ref:`firstresult`. - The return value is not used, but only stops further processing. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. - """ - - -def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None: - """Called at the start of running the runtest protocol for a single item. - - See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - - :param nodeid: Full node ID of the item. - :param location: A tuple of ``(filename, lineno, testname)`` - where ``filename`` is a file path relative to ``config.rootpath`` - and ``lineno`` is 0-based. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_runtest_logfinish( - nodeid: str, location: tuple[str, int | None, str] -) -> None: - """Called at the end of running the runtest protocol for a single item. - - See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - - :param nodeid: Full node ID of the item. - :param location: A tuple of ``(filename, lineno, testname)`` - where ``filename`` is a file path relative to ``config.rootpath`` - and ``lineno`` is 0-based. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_runtest_setup(item: Item) -> None: - """Called to perform the setup phase for a test item. - - The default implementation runs ``setup()`` on ``item`` and all of its - parents (which haven't been setup yet). This includes obtaining the - values of fixtures required by the item (which haven't been obtained - yet). - - :param item: - The item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_runtest_call(item: Item) -> None: - """Called to run the test for test item (the call phase). - - The default implementation calls ``item.runtest()``. - - :param item: - The item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: - """Called to perform the teardown phase for a test item. - - The default implementation runs the finalizers and calls ``teardown()`` - on ``item`` and all of its parents (which need to be torn down). This - includes running the teardown phase of fixtures required by the item (if - they go out of scope). - - :param item: - The item. - :param nextitem: - The scheduled-to-be-next test item (None if no further test item is - scheduled). This argument is used to perform exact teardowns, i.e. - calling just enough finalizers so that nextitem only needs to call - setup functions. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -@hookspec(firstresult=True) -def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: - """Called to create a :class:`~pytest.TestReport` for each of - the setup, call and teardown runtest phases of a test item. - - See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - - :param item: The item. - :param call: The :class:`~pytest.CallInfo` for the phase. - - Stops at first non-None result, see :ref:`firstresult`. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_runtest_logreport(report: TestReport) -> None: - """Process the :class:`~pytest.TestReport` produced for each - of the setup, call and teardown runtest phases of an item. - - See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -@hookspec(firstresult=True) -def pytest_report_to_serializable( - config: Config, - report: CollectReport | TestReport, -) -> dict[str, Any] | None: - """Serialize the given report object into a data structure suitable for - sending over the wire, e.g. converted to JSON. - - :param config: The pytest config object. - :param report: The report. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. The exact details may depend - on the plugin which calls the hook. - """ - - -@hookspec(firstresult=True) -def pytest_report_from_serializable( - config: Config, - data: dict[str, Any], -) -> CollectReport | TestReport | None: - """Restore a report object previously serialized with - :hook:`pytest_report_to_serializable`. - - :param config: The pytest config object. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. The exact details may depend - on the plugin which calls the hook. - """ - - -# ------------------------------------------------------------------------- -# Fixture related hooks -# ------------------------------------------------------------------------- - - -@hookspec(firstresult=True) -def pytest_fixture_setup( - fixturedef: FixtureDef[Any], request: SubRequest -) -> object | None: - """Perform fixture setup execution. - - :param fixturedef: - The fixture definition object. - :param request: - The fixture request object. - :returns: - The return value of the call to the fixture function. - - Stops at first non-None result, see :ref:`firstresult`. - - .. note:: - If the fixture function returns None, other implementations of - this hook function will continue to be called, according to the - behavior of the :ref:`firstresult` option. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given fixture, only - conftest files in the fixture scope's directory and its parent directories - are consulted. - """ - - -def pytest_fixture_post_finalizer( - fixturedef: FixtureDef[Any], request: SubRequest -) -> None: - """Called after fixture teardown, but before the cache is cleared, so - the fixture result ``fixturedef.cached_result`` is still available (not - ``None``). - - :param fixturedef: - The fixture definition object. - :param request: - The fixture request object. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given fixture, only - conftest files in the fixture scope's directory and its parent directories - are consulted. - """ - - -# ------------------------------------------------------------------------- -# test session related hooks -# ------------------------------------------------------------------------- - - -def pytest_sessionstart(session: Session) -> None: - """Called after the ``Session`` object has been created and before performing collection - and entering the run test loop. - - :param session: The pytest session object. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. - """ - - -def pytest_sessionfinish( - session: Session, - exitstatus: int | ExitCode, -) -> None: - """Called after whole test run finished, right before returning the exit status to the system. - - :param session: The pytest session object. - :param exitstatus: The status which pytest will return to the system. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. - """ - - -def pytest_unconfigure(config: Config) -> None: - """Called before test process is exited. - - :param config: The pytest config object. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. - """ - - -# ------------------------------------------------------------------------- -# hooks for customizing the assert methods -# ------------------------------------------------------------------------- - - -def pytest_assertrepr_compare( - config: Config, op: str, left: object, right: object -) -> list[str] | None: - """Return explanation for comparisons in failing assert expressions. - - Return None for no custom explanation, otherwise return a list - of strings. The strings will be joined by newlines but any newlines - *in* a string will be escaped. Note that all but the first line will - be indented slightly, the intention is for the first line to be a summary. - - :param config: The pytest config object. - :param op: The operator, e.g. `"=="`, `"!="`, `"not in"`. - :param left: The left operand. - :param right: The right operand. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: - """Called whenever an assertion passes. - - .. versionadded:: 5.0 - - Use this hook to do some processing after a passing assertion. - The original assertion information is available in the `orig` string - and the pytest introspected assertion information is available in the - `expl` string. - - This hook must be explicitly enabled by the :confval:`enable_assertion_pass_hook` - configuration option: - - .. tab:: toml - - .. code-block:: toml - - [pytest] - enable_assertion_pass_hook = true - - .. tab:: ini - - .. code-block:: ini - - [pytest] - enable_assertion_pass_hook = true - - You need to **clean the .pyc** files in your project directory and interpreter libraries - when enabling this option, as assertions will require to be re-written. - - :param item: pytest item object of current test. - :param lineno: Line number of the assert statement. - :param orig: String with the original assertion. - :param expl: String with the assert explanation. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. - """ - - -# ------------------------------------------------------------------------- -# Hooks for influencing reporting (invoked from _pytest_terminal). -# ------------------------------------------------------------------------- - - -def pytest_report_header(config: Config, start_path: Path) -> str | list[str]: # type: ignore[empty-body] - """Return a string or list of strings to be displayed as header info for terminal reporting. - - :param config: The pytest config object. - :param start_path: The starting dir. - :type start_path: pathlib.Path - - .. note:: - - Lines returned by a plugin are displayed before those of plugins which - ran before it. - If you want to have your line(s) displayed first, use - :ref:`trylast=True `. - - .. versionchanged:: 7.0.0 - The ``start_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``startdir`` parameter. The ``startdir`` parameter - has been deprecated and removed in pytest 9.0.0. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. - """ - - -def pytest_report_collectionfinish( # type: ignore[empty-body] - config: Config, - start_path: Path, - items: Sequence[Item], -) -> str | list[str]: - """Return a string or list of strings to be displayed after collection - has finished successfully. - - These strings will be displayed after the standard "collected X items" message. - - .. versionadded:: 3.2 - - :param config: The pytest config object. - :param start_path: The starting dir. - :type start_path: pathlib.Path - :param items: List of pytest items that are going to be executed; this list should not be modified. - - .. note:: - - Lines returned by a plugin are displayed before those of plugins which - ran before it. - If you want to have your line(s) displayed first, use - :ref:`trylast=True `. - - .. versionchanged:: 7.0.0 - The ``start_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``startdir`` parameter. The ``startdir`` parameter - has been deprecated and removed in pytest 9.0.0. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -@hookspec(firstresult=True) -def pytest_report_teststatus( # type:ignore[empty-body] - report: CollectReport | TestReport, config: Config -) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]: - """Return result-category, shortletter and verbose word for status - reporting. - - The result-category is a category in which to count the result, for - example "passed", "skipped", "error" or the empty string. - - The shortletter is shown as testing progresses, for example ".", "s", - "E" or the empty string. - - The verbose word is shown as testing progresses in verbose mode, for - example "PASSED", "SKIPPED", "ERROR" or the empty string. - - pytest may style these implicitly according to the report outcome. - To provide explicit styling, return a tuple for the verbose word, - for example ``"rerun", "R", ("RERUN", {"yellow": True})``. - - :param report: The report object whose status is to be returned. - :param config: The pytest config object. - :returns: The test status. - - Stops at first non-None result, see :ref:`firstresult`. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -def pytest_terminal_summary( - terminalreporter: TerminalReporter, - exitstatus: ExitCode, - config: Config, -) -> None: - """Add a section to terminal summary reporting. - - :param terminalreporter: The internal terminal reporter object. - :param exitstatus: The exit status that will be reported back to the OS. - :param config: The pytest config object. - - .. versionadded:: 4.2 - The ``config`` parameter. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -@hookspec(historic=True) -def pytest_warning_recorded( - warning_message: warnings.WarningMessage, - when: Literal["config", "collect", "runtest"], - nodeid: str, - location: tuple[str, int, str] | None, -) -> None: - """Process a warning captured by the internal pytest warnings plugin. - - :param warning_message: - The captured warning. This is the same object produced by :class:`warnings.catch_warnings`, - and contains the same attributes as the parameters of :py:func:`warnings.showwarning`. - - :param when: - Indicates when the warning was captured. Possible values: - - * ``"config"``: during pytest configuration/initialization stage. - * ``"collect"``: during test collection. - * ``"runtest"``: during test execution. - - :param nodeid: - Full id of the item. Empty string for warnings that are not specific to - a particular node. - - :param location: - When available, holds information about the execution context of the captured - warning (filename, linenumber, function). ``function`` evaluates to - when the execution context is at the module level. - - .. versionadded:: 6.0 - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. If the warning is specific to a - particular node, only conftest files in parent directories of the node are - consulted. - """ - - -# ------------------------------------------------------------------------- -# Hooks for influencing skipping -# ------------------------------------------------------------------------- - - -def pytest_markeval_namespace( # type:ignore[empty-body] - config: Config, -) -> dict[str, Any]: - """Called when constructing the globals dictionary used for - evaluating string conditions in xfail/skipif markers. - - This is useful when the condition for a marker requires - objects that are expensive or impossible to obtain during - collection time, which is required by normal boolean - conditions. - - .. versionadded:: 6.2 - - :param config: The pytest config object. - :returns: A dictionary of additional globals to add. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in parent directories of the item are consulted. - """ - - -# ------------------------------------------------------------------------- -# error handling and internal debugging hooks -# ------------------------------------------------------------------------- - - -def pytest_internalerror( - excrepr: ExceptionRepr, - excinfo: ExceptionInfo[BaseException], -) -> bool | None: - """Called for internal errors. - - Return True to suppress the fallback handling of printing an - INTERNALERROR message directly to sys.stderr. - - :param excrepr: The exception repr object. - :param excinfo: The exception info. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -def pytest_keyboard_interrupt( - excinfo: ExceptionInfo[KeyboardInterrupt | Exit], -) -> None: - """Called for keyboard interrupt. - - :param excinfo: The exception info. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -def pytest_exception_interact( - node: Item | Collector, - call: CallInfo[Any], - report: CollectReport | TestReport, -) -> None: - """Called when an exception was raised which can potentially be - interactively handled. - - May be called during collection (see :hook:`pytest_make_collect_report`), - in which case ``report`` is a :class:`~pytest.CollectReport`. - - May be called during runtest of an item (see :hook:`pytest_runtest_protocol`), - in which case ``report`` is a :class:`~pytest.TestReport`. - - This hook is not called if the exception that was raised is an internal - exception like ``skip.Exception``. - - :param node: - The item or collector. - :param call: - The call information. Contains the exception. - :param report: - The collection or test report. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given node, only conftest - files in parent directories of the node are consulted. - """ - - -def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: - """Called upon pdb.set_trace(). - - Can be used by plugins to take special action just before the python - debugger enters interactive mode. - - :param config: The pytest config object. - :param pdb: The Pdb instance. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ - - -def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: - """Called when leaving pdb (e.g. with continue after pdb.set_trace()). - - Can be used by plugins to take special action just after the python - debugger leaves interactive mode. - - :param config: The pytest config object. - :param pdb: The Pdb instance. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. - """ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/junitxml.py b/tests/venv2/lib/python3.11/site-packages/_pytest/junitxml.py deleted file mode 100644 index ac78f50..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/junitxml.py +++ /dev/null @@ -1,703 +0,0 @@ -# mypy: allow-untyped-defs -"""Report test results in JUnit-XML format, for use with Jenkins and build -integration servers. - -Based on initial code from Ross Lawley. - -Output conforms to -https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd -""" - -from __future__ import annotations - -from collections.abc import Callable -import functools -import os -import platform -import re -import xml.etree.ElementTree as ET - -from _pytest import nodes -from _pytest import timing -from _pytest._code.code import ExceptionRepr -from _pytest._code.code import ReprFileLocation -from _pytest.config import Config -from _pytest.config import filename_arg -from _pytest.config.argparsing import Parser -from _pytest.fixtures import FixtureRequest -from _pytest.reports import TestReport -from _pytest.stash import StashKey -from _pytest.terminal import TerminalReporter -import pytest - - -xml_key = StashKey["LogXML"]() - - -def bin_xml_escape(arg: object) -> str: - r"""Visually escape invalid XML characters. - - For example, transforms - 'hello\aworld\b' - into - 'hello#x07world#x08' - Note that the #xABs are *not* XML escapes - missing the ampersand «. - The idea is to escape visually for the user rather than for XML itself. - """ - - def repl(matchobj: re.Match[str]) -> str: - i = ord(matchobj.group()) - if i <= 0xFF: - return f"#x{i:02X}" - else: - return f"#x{i:04X}" - - # The spec range of valid chars is: - # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - # For an unknown(?) reason, we disallow #x7F (DEL) as well. - illegal_xml_re = "[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]" - return re.sub(illegal_xml_re, repl, str(arg)) - - -def merge_family(left, right) -> None: - result = {} - for kl, vl in left.items(): - for kr, vr in right.items(): - if not isinstance(vl, list): - raise TypeError(type(vl)) - result[kl] = vl + vr - left.update(result) - - -families = { # pylint: disable=dict-init-mutate - "_base": {"testcase": ["classname", "name"]}, - "_base_legacy": {"testcase": ["file", "line", "url"]}, -} -# xUnit 1.x inherits legacy attributes. -families["xunit1"] = families["_base"].copy() -merge_family(families["xunit1"], families["_base_legacy"]) - -# xUnit 2.x uses strict base attributes. -families["xunit2"] = families["_base"] - - -class _NodeReporter: - def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: - self.id = nodeid - self.xml = xml - self.add_stats = self.xml.add_stats - self.family = self.xml.family - self.duration = 0.0 - self.properties: list[tuple[str, str]] = [] - self.nodes: list[ET.Element] = [] - self.attrs: dict[str, str] = {} - - def append(self, node: ET.Element) -> None: - self.xml.add_stats(node.tag) - self.nodes.append(node) - - def add_property(self, name: str, value: object) -> None: - self.properties.append((str(name), bin_xml_escape(value))) - - def add_attribute(self, name: str, value: object) -> None: - self.attrs[str(name)] = bin_xml_escape(value) - - def make_properties_node(self) -> ET.Element | None: - """Return a Junit node containing custom properties, if any.""" - if self.properties: - properties = ET.Element("properties") - for name, value in self.properties: - properties.append(ET.Element("property", name=name, value=value)) - return properties - return None - - def record_testreport(self, testreport: TestReport) -> None: - names = mangle_test_address(testreport.nodeid) - existing_attrs = self.attrs - classnames = names[:-1] - if self.xml.prefix: - classnames.insert(0, self.xml.prefix) - attrs: dict[str, str] = { - "classname": ".".join(classnames), - "name": bin_xml_escape(names[-1]), - "file": testreport.location[0], - } - if testreport.location[1] is not None: - attrs["line"] = str(testreport.location[1]) - if hasattr(testreport, "url"): - attrs["url"] = testreport.url - self.attrs = attrs - self.attrs.update(existing_attrs) # Restore any user-defined attributes. - - # Preserve legacy testcase behavior. - if self.family == "xunit1": - return - - # Filter out attributes not permitted by this test family. - # Including custom attributes because they are not valid here. - temp_attrs = {} - for key in self.attrs: - if key in families[self.family]["testcase"]: - temp_attrs[key] = self.attrs[key] - self.attrs = temp_attrs - - def to_xml(self) -> ET.Element: - testcase = ET.Element("testcase", self.attrs, time=f"{self.duration:.3f}") - properties = self.make_properties_node() - if properties is not None: - testcase.append(properties) - testcase.extend(self.nodes) - return testcase - - def _add_simple(self, tag: str, message: str, data: str | None = None) -> None: - node = ET.Element(tag, message=message) - node.text = bin_xml_escape(data) - self.append(node) - - def write_captured_output(self, report: TestReport) -> None: - if not self.xml.log_passing_tests and report.passed: - return - - content_out = report.capstdout - content_log = report.caplog - content_err = report.capstderr - if self.xml.logging == "no": - return - content_all = "" - if self.xml.logging in ["log", "all"]: - content_all = self._prepare_content(content_log, " Captured Log ") - if self.xml.logging in ["system-out", "out-err", "all"]: - content_all += self._prepare_content(content_out, " Captured Out ") - self._write_content(report, content_all, "system-out") - content_all = "" - if self.xml.logging in ["system-err", "out-err", "all"]: - content_all += self._prepare_content(content_err, " Captured Err ") - self._write_content(report, content_all, "system-err") - content_all = "" - if content_all: - self._write_content(report, content_all, "system-out") - - def _prepare_content(self, content: str, header: str) -> str: - return "\n".join([header.center(80, "-"), content, ""]) - - def _write_content(self, report: TestReport, content: str, jheader: str) -> None: - tag = ET.Element(jheader) - tag.text = bin_xml_escape(content) - self.append(tag) - - def append_pass(self, report: TestReport) -> None: - self.add_stats("passed") - - def append_failure(self, report: TestReport) -> None: - # msg = str(report.longrepr.reprtraceback.extraline) - if hasattr(report, "wasxfail"): - self._add_simple("skipped", "xfail-marked test passes unexpectedly") - else: - assert report.longrepr is not None - reprcrash: ReprFileLocation | None = getattr( - report.longrepr, "reprcrash", None - ) - if reprcrash is not None: - message = reprcrash.message - else: - message = str(report.longrepr) - message = bin_xml_escape(message) - self._add_simple("failure", message, str(report.longrepr)) - - def append_collect_error(self, report: TestReport) -> None: - # msg = str(report.longrepr.reprtraceback.extraline) - assert report.longrepr is not None - self._add_simple("error", "collection failure", str(report.longrepr)) - - def append_collect_skipped(self, report: TestReport) -> None: - self._add_simple("skipped", "collection skipped", str(report.longrepr)) - - def append_error(self, report: TestReport) -> None: - assert report.longrepr is not None - reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) - if reprcrash is not None: - reason = reprcrash.message - else: - reason = str(report.longrepr) - - if report.when == "teardown": - msg = f'failed on teardown with "{reason}"' - else: - msg = f'failed on setup with "{reason}"' - self._add_simple("error", bin_xml_escape(msg), str(report.longrepr)) - - def append_skipped(self, report: TestReport) -> None: - if hasattr(report, "wasxfail"): - xfailreason = report.wasxfail - if xfailreason.startswith("reason: "): - xfailreason = xfailreason[8:] - xfailreason = bin_xml_escape(xfailreason) - skipped = ET.Element("skipped", type="pytest.xfail", message=xfailreason) - self.append(skipped) - else: - assert isinstance(report.longrepr, tuple) - filename, lineno, skipreason = report.longrepr - if skipreason.startswith("Skipped: "): - skipreason = skipreason[9:] - details = f"{filename}:{lineno}: {skipreason}" - - skipped = ET.Element( - "skipped", type="pytest.skip", message=bin_xml_escape(skipreason) - ) - skipped.text = bin_xml_escape(details) - self.append(skipped) - self.write_captured_output(report) - - def finalize(self) -> None: - data = self.to_xml() - self.__dict__.clear() - # Type ignored because mypy doesn't like overriding a method. - # Also the return value doesn't match... - self.to_xml = lambda: data # type: ignore[method-assign] - - -def _warn_incompatibility_with_xunit2( - request: FixtureRequest, fixture_name: str -) -> None: - """Emit a PytestWarning about the given fixture being incompatible with newer xunit revisions.""" - from _pytest.warning_types import PytestWarning - - xml = request.config.stash.get(xml_key, None) - if xml is not None and xml.family not in ("xunit1", "legacy"): - request.node.warn( - PytestWarning( - f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')" - ) - ) - - -@pytest.fixture -def record_property(request: FixtureRequest) -> Callable[[str, object], None]: - """Add extra properties to the calling test. - - User properties become part of the test report and are available to the - configured reporters, like JUnit XML. - - The fixture is callable with ``name, value``. The value is automatically - XML-encoded. - - Example:: - - def test_function(record_property): - record_property("example_key", 1) - """ - _warn_incompatibility_with_xunit2(request, "record_property") - - def append_property(name: str, value: object) -> None: - request.node.user_properties.append((name, value)) - - return append_property - - -@pytest.fixture -def record_xml_attribute(request: FixtureRequest) -> Callable[[str, object], None]: - """Add extra xml attributes to the tag for the calling test. - - The fixture is callable with ``name, value``. The value is - automatically XML-encoded. - """ - from _pytest.warning_types import PytestExperimentalApiWarning - - request.node.warn( - PytestExperimentalApiWarning("record_xml_attribute is an experimental feature") - ) - - _warn_incompatibility_with_xunit2(request, "record_xml_attribute") - - # Declare noop - def add_attr_noop(name: str, value: object) -> None: - pass - - attr_func = add_attr_noop - - xml = request.config.stash.get(xml_key, None) - if xml is not None: - node_reporter = xml.node_reporter(request.node.nodeid) - attr_func = node_reporter.add_attribute - - return attr_func - - -def _check_record_param_type(param: str, v: str) -> None: - """Used by record_testsuite_property to check that the given parameter name is of the proper - type.""" - __tracebackhide__ = True - if not isinstance(v, str): - msg = "{param} parameter needs to be a string, but {g} given" # type: ignore[unreachable] - raise TypeError(msg.format(param=param, g=type(v).__name__)) - - -@pytest.fixture(scope="session") -def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object], None]: - """Record a new ```` tag as child of the root ````. - - This is suitable to writing global information regarding the entire test - suite, and is compatible with ``xunit2`` JUnit family. - - This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: - - .. code-block:: python - - def test_foo(record_testsuite_property): - record_testsuite_property("ARCH", "PPC") - record_testsuite_property("STORAGE_TYPE", "CEPH") - - :param name: - The property name. - :param value: - The property value. Will be converted to a string. - - .. warning:: - - Currently this fixture **does not work** with the - `pytest-xdist `__ plugin. See - :issue:`7767` for details. - """ - __tracebackhide__ = True - - def record_func(name: str, value: object) -> None: - """No-op function in case --junit-xml was not passed in the command-line.""" - __tracebackhide__ = True - _check_record_param_type("name", name) - - xml = request.config.stash.get(xml_key, None) - if xml is not None: - record_func = xml.add_global_property - return record_func - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting") - group.addoption( - "--junitxml", - "--junit-xml", - action="store", - dest="xmlpath", - metavar="path", - type=functools.partial(filename_arg, optname="--junitxml"), - default=None, - help="Create junit-xml style report file at given path", - ) - group.addoption( - "--junitprefix", - "--junit-prefix", - action="store", - metavar="str", - default=None, - help="Prepend prefix to classnames in junit-xml output", - ) - parser.addini( - "junit_suite_name", "Test suite name for JUnit report", default="pytest" - ) - parser.addini( - "junit_logging", - "Write captured log messages to JUnit report: " - "one of no|log|system-out|system-err|out-err|all", - default="no", - ) - parser.addini( - "junit_log_passing_tests", - "Capture log information for passing tests to JUnit report: ", - type="bool", - default=True, - ) - parser.addini( - "junit_duration_report", - "Duration time to report: one of total|call", - default="total", - ) # choices=['total', 'call']) - parser.addini( - "junit_family", - "Emit XML for schema: one of legacy|xunit1|xunit2", - default="xunit2", - ) - - -def pytest_configure(config: Config) -> None: - xmlpath = config.option.xmlpath - # Prevent opening xmllog on worker nodes (xdist). - if xmlpath and not hasattr(config, "workerinput"): - junit_family = config.getini("junit_family") - config.stash[xml_key] = LogXML( - xmlpath, - config.option.junitprefix, - config.getini("junit_suite_name"), - config.getini("junit_logging"), - config.getini("junit_duration_report"), - junit_family, - config.getini("junit_log_passing_tests"), - ) - config.pluginmanager.register(config.stash[xml_key]) - - -def pytest_unconfigure(config: Config) -> None: - xml = config.stash.get(xml_key, None) - if xml: - del config.stash[xml_key] - config.pluginmanager.unregister(xml) - - -def mangle_test_address(address: str) -> list[str]: - path, possible_open_bracket, params = address.partition("[") - names = path.split("::") - # Convert file path to dotted path. - names[0] = names[0].replace(nodes.SEP, ".") - names[0] = re.sub(r"\.py$", "", names[0]) - # Put any params back. - names[-1] += possible_open_bracket + params - return names - - -class LogXML: - def __init__( - self, - logfile, - prefix: str | None, - suite_name: str = "pytest", - logging: str = "no", - report_duration: str = "total", - family="xunit1", - log_passing_tests: bool = True, - ) -> None: - logfile = os.path.expanduser(os.path.expandvars(logfile)) - self.logfile = os.path.normpath(os.path.abspath(logfile)) - self.prefix = prefix - self.suite_name = suite_name - self.logging = logging - self.log_passing_tests = log_passing_tests - self.report_duration = report_duration - self.family = family - self.stats: dict[str, int] = dict.fromkeys( - ["error", "passed", "failure", "skipped"], 0 - ) - self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {} - self.node_reporters_ordered: list[_NodeReporter] = [] - self.global_properties: list[tuple[str, str]] = [] - - # List of reports that failed on call but teardown is pending. - self.open_reports: list[TestReport] = [] - self.cnt_double_fail_tests = 0 - - # Replaces convenience family with real family. - if self.family == "legacy": - self.family = "xunit1" - - def finalize(self, report: TestReport) -> None: - nodeid = getattr(report, "nodeid", report) - # Local hack to handle xdist report order. - workernode = getattr(report, "node", None) - reporter = self.node_reporters.pop((nodeid, workernode)) - - for propname, propvalue in report.user_properties: - reporter.add_property(propname, str(propvalue)) - - if reporter is not None: - reporter.finalize() - - def node_reporter(self, report: TestReport | str) -> _NodeReporter: - nodeid: str | TestReport = getattr(report, "nodeid", report) - # Local hack to handle xdist report order. - workernode = getattr(report, "node", None) - - key = nodeid, workernode - - if key in self.node_reporters: - # TODO: breaks for --dist=each - return self.node_reporters[key] - - reporter = _NodeReporter(nodeid, self) - - self.node_reporters[key] = reporter - self.node_reporters_ordered.append(reporter) - - return reporter - - def add_stats(self, key: str) -> None: - if key in self.stats: - self.stats[key] += 1 - - def _opentestcase(self, report: TestReport) -> _NodeReporter: - reporter = self.node_reporter(report) - reporter.record_testreport(report) - return reporter - - def pytest_runtest_logreport(self, report: TestReport) -> None: - """Handle a setup/call/teardown report, generating the appropriate - XML tags as necessary. - - Note: due to plugins like xdist, this hook may be called in interlaced - order with reports from other nodes. For example: - - Usual call order: - -> setup node1 - -> call node1 - -> teardown node1 - -> setup node2 - -> call node2 - -> teardown node2 - - Possible call order in xdist: - -> setup node1 - -> call node1 - -> setup node2 - -> call node2 - -> teardown node2 - -> teardown node1 - """ - close_report = None - if report.passed: - if report.when == "call": # ignore setup/teardown - reporter = self._opentestcase(report) - reporter.append_pass(report) - elif report.failed: - if report.when == "teardown": - # The following vars are needed when xdist plugin is used. - report_wid = getattr(report, "worker_id", None) - report_ii = getattr(report, "item_index", None) - close_report = next( - ( - rep - for rep in self.open_reports - if ( - rep.nodeid == report.nodeid - and getattr(rep, "item_index", None) == report_ii - and getattr(rep, "worker_id", None) == report_wid - ) - ), - None, - ) - if close_report: - # We need to open new testcase in case we have failure in - # call and error in teardown in order to follow junit - # schema. - self.finalize(close_report) - else: - # A passing call with a teardown error creates separate - # terminal reports, but JUnit XML keeps one testcase - # element for that item (#3850). - self.cnt_double_fail_tests += int( - ( - report.nodeid, - getattr(report, "node", None), - ) - in self.node_reporters - ) - reporter = self._opentestcase(report) - if report.when == "call": - reporter.append_failure(report) - self.open_reports.append(report) - if not self.log_passing_tests: - reporter.write_captured_output(report) - else: - reporter.append_error(report) - elif report.skipped: - reporter = self._opentestcase(report) - reporter.append_skipped(report) - self.update_testcase_duration(report) - if report.when == "teardown": - reporter = self._opentestcase(report) - reporter.write_captured_output(report) - - self.finalize(report) - report_wid = getattr(report, "worker_id", None) - report_ii = getattr(report, "item_index", None) - close_report = next( - ( - rep - for rep in self.open_reports - if ( - rep.nodeid == report.nodeid - and getattr(rep, "item_index", None) == report_ii - and getattr(rep, "worker_id", None) == report_wid - ) - ), - None, - ) - if close_report: - self.open_reports.remove(close_report) - - def update_testcase_duration(self, report: TestReport) -> None: - """Accumulate total duration for nodeid from given report and update - the Junit.testcase with the new total if already created.""" - if self.report_duration in {"total", report.when}: - reporter = self.node_reporter(report) - reporter.duration += getattr(report, "duration", 0.0) - - def pytest_collectreport(self, report: TestReport) -> None: - if not report.passed: - reporter = self._opentestcase(report) - if report.failed: - reporter.append_collect_error(report) - else: - reporter.append_collect_skipped(report) - - def pytest_internalerror(self, excrepr: ExceptionRepr) -> None: - reporter = self.node_reporter("internal") - reporter.attrs.update(classname="pytest", name="internal") - reporter._add_simple("error", "internal error", str(excrepr)) - - def pytest_sessionstart(self) -> None: - self.suite_start = timing.Instant() - - def pytest_sessionfinish(self) -> None: - dirname = os.path.dirname(os.path.abspath(self.logfile)) - # exist_ok avoids filesystem race conditions between checking path existence and requesting creation - os.makedirs(dirname, exist_ok=True) - - with open(self.logfile, "w", encoding="utf-8") as logfile: - duration = self.suite_start.elapsed() - - numtests = ( - self.stats["passed"] - + self.stats["failure"] - + self.stats["skipped"] - + self.stats["error"] - - self.cnt_double_fail_tests - ) - logfile.write('') - - suite_node = ET.Element( - "testsuite", - name=self.suite_name, - errors=str(self.stats["error"]), - failures=str(self.stats["failure"]), - skipped=str(self.stats["skipped"]), - tests=str(numtests), - time=f"{duration.seconds:.3f}", - timestamp=self.suite_start.as_utc().astimezone().isoformat(), - hostname=platform.node(), - ) - global_properties = self._get_global_properties_node() - if global_properties is not None: - suite_node.append(global_properties) - for node_reporter in self.node_reporters_ordered: - suite_node.append(node_reporter.to_xml()) - testsuites = ET.Element("testsuites") - testsuites.set("name", "pytest tests") - testsuites.append(suite_node) - logfile.write(ET.tostring(testsuites, encoding="unicode")) - - def pytest_terminal_summary( - self, terminalreporter: TerminalReporter, config: pytest.Config - ) -> None: - if config.get_verbosity() >= 0: - terminalreporter.write_sep("-", f"generated xml file: {self.logfile}") - - def add_global_property(self, name: str, value: object) -> None: - __tracebackhide__ = True - _check_record_param_type("name", name) - self.global_properties.append((name, bin_xml_escape(value))) - - def _get_global_properties_node(self) -> ET.Element | None: - """Return a Junit node containing custom properties, if any.""" - if self.global_properties: - properties = ET.Element("properties") - for name, value in self.global_properties: - properties.append(ET.Element("property", name=name, value=value)) - return properties - return None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/legacypath.py b/tests/venv2/lib/python3.11/site-packages/_pytest/legacypath.py deleted file mode 100644 index 59e8ef6..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/legacypath.py +++ /dev/null @@ -1,468 +0,0 @@ -# mypy: allow-untyped-defs -"""Add backward compatibility support for the legacy py path type.""" - -from __future__ import annotations - -import dataclasses -from pathlib import Path -import shlex -import subprocess -from typing import Final -from typing import final -from typing import TYPE_CHECKING - -from iniconfig import SectionWrapper - -from _pytest.cacheprovider import Cache -from _pytest.compat import LEGACY_PATH -from _pytest.compat import legacy_path -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config import PytestPluginManager -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import FixtureRequest -from _pytest.main import Session -from _pytest.monkeypatch import MonkeyPatch -from _pytest.nodes import Collector -from _pytest.nodes import Item -from _pytest.nodes import Node -from _pytest.pytester import HookRecorder -from _pytest.pytester import Pytester -from _pytest.pytester import RunResult -from _pytest.terminal import TerminalReporter -from _pytest.tmpdir import TempPathFactory - - -if TYPE_CHECKING: - import pexpect - - -@final -class Testdir: - """ - Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead. - - All methods just forward to an internal :class:`Pytester` instance, converting results - to `legacy_path` objects as necessary. - """ - - __test__ = False - - CLOSE_STDIN: Final = Pytester.CLOSE_STDIN - TimeoutExpired: Final = Pytester.TimeoutExpired - - def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: - check_ispytest(_ispytest) - self._pytester = pytester - - @property - def tmpdir(self) -> LEGACY_PATH: - """Temporary directory where tests are executed.""" - return legacy_path(self._pytester.path) - - @property - def test_tmproot(self) -> LEGACY_PATH: - return legacy_path(self._pytester._test_tmproot) - - @property - def request(self): - return self._pytester._request - - @property - def plugins(self): - return self._pytester.plugins - - @plugins.setter - def plugins(self, plugins): - self._pytester.plugins = plugins - - @property - def monkeypatch(self) -> MonkeyPatch: - return self._pytester._monkeypatch - - def make_hook_recorder(self, pluginmanager) -> HookRecorder: - """See :meth:`Pytester.make_hook_recorder`.""" - return self._pytester.make_hook_recorder(pluginmanager) - - def chdir(self) -> None: - """See :meth:`Pytester.chdir`.""" - return self._pytester.chdir() - - def finalize(self) -> None: - return self._pytester._finalize() - - def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: - """See :meth:`Pytester.makefile`.""" - if ext and not ext.startswith("."): - # pytester.makefile is going to throw a ValueError in a way that - # testdir.makefile did not, because - # pathlib.Path is stricter suffixes than py.path - # This ext arguments is likely user error, but since testdir has - # allowed this, we will prepend "." as a workaround to avoid breaking - # testdir usage that worked before - ext = "." + ext - return legacy_path(self._pytester.makefile(ext, *args, **kwargs)) - - def makeconftest(self, source) -> LEGACY_PATH: - """See :meth:`Pytester.makeconftest`.""" - return legacy_path(self._pytester.makeconftest(source)) - - def makeini(self, source) -> LEGACY_PATH: - """See :meth:`Pytester.makeini`.""" - return legacy_path(self._pytester.makeini(source)) - - def getinicfg(self, source: str) -> SectionWrapper: - """See :meth:`Pytester.getinicfg`.""" - return self._pytester.getinicfg(source) - - def makepyprojecttoml(self, source) -> LEGACY_PATH: - """See :meth:`Pytester.makepyprojecttoml`.""" - return legacy_path(self._pytester.makepyprojecttoml(source)) - - def makepyfile(self, *args, **kwargs) -> LEGACY_PATH: - """See :meth:`Pytester.makepyfile`.""" - return legacy_path(self._pytester.makepyfile(*args, **kwargs)) - - def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH: - """See :meth:`Pytester.maketxtfile`.""" - return legacy_path(self._pytester.maketxtfile(*args, **kwargs)) - - def syspathinsert(self, path=None) -> None: - """See :meth:`Pytester.syspathinsert`.""" - return self._pytester.syspathinsert(path) - - def mkdir(self, name) -> LEGACY_PATH: - """See :meth:`Pytester.mkdir`.""" - return legacy_path(self._pytester.mkdir(name)) - - def mkpydir(self, name) -> LEGACY_PATH: - """See :meth:`Pytester.mkpydir`.""" - return legacy_path(self._pytester.mkpydir(name)) - - def copy_example(self, name=None) -> LEGACY_PATH: - """See :meth:`Pytester.copy_example`.""" - return legacy_path(self._pytester.copy_example(name)) - - def getnode(self, config: Config, arg) -> Item | Collector | None: - """See :meth:`Pytester.getnode`.""" - return self._pytester.getnode(config, arg) - - def getpathnode(self, path): - """See :meth:`Pytester.getpathnode`.""" - return self._pytester.getpathnode(path) - - def genitems(self, colitems: list[Item | Collector]) -> list[Item]: - """See :meth:`Pytester.genitems`.""" - return self._pytester.genitems(colitems) - - def runitem(self, source): - """See :meth:`Pytester.runitem`.""" - return self._pytester.runitem(source) - - def inline_runsource(self, source, *cmdlineargs): - """See :meth:`Pytester.inline_runsource`.""" - return self._pytester.inline_runsource(source, *cmdlineargs) - - def inline_genitems(self, *args): - """See :meth:`Pytester.inline_genitems`.""" - return self._pytester.inline_genitems(*args) - - def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False): - """See :meth:`Pytester.inline_run`.""" - return self._pytester.inline_run( - *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc - ) - - def runpytest_inprocess(self, *args, **kwargs) -> RunResult: - """See :meth:`Pytester.runpytest_inprocess`.""" - return self._pytester.runpytest_inprocess(*args, **kwargs) - - def runpytest(self, *args, **kwargs) -> RunResult: - """See :meth:`Pytester.runpytest`.""" - return self._pytester.runpytest(*args, **kwargs) - - def parseconfig(self, *args) -> Config: - """See :meth:`Pytester.parseconfig`.""" - return self._pytester.parseconfig(*args) - - def parseconfigure(self, *args) -> Config: - """See :meth:`Pytester.parseconfigure`.""" - return self._pytester.parseconfigure(*args) - - def getitem(self, source, funcname="test_func"): - """See :meth:`Pytester.getitem`.""" - return self._pytester.getitem(source, funcname) - - def getitems(self, source): - """See :meth:`Pytester.getitems`.""" - return self._pytester.getitems(source) - - def getmodulecol(self, source, configargs=(), withinit=False): - """See :meth:`Pytester.getmodulecol`.""" - return self._pytester.getmodulecol( - source, configargs=configargs, withinit=withinit - ) - - def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: - """See :meth:`Pytester.collect_by_name`.""" - return self._pytester.collect_by_name(modcol, name) - - def popen( - self, - cmdargs, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=CLOSE_STDIN, - **kw, - ): - """See :meth:`Pytester.popen`.""" - return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw) - - def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult: - """See :meth:`Pytester.run`.""" - return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin) - - def runpython(self, script) -> RunResult: - """See :meth:`Pytester.runpython`.""" - return self._pytester.runpython(script) - - def runpython_c(self, command): - """See :meth:`Pytester.runpython_c`.""" - return self._pytester.runpython_c(command) - - def runpytest_subprocess(self, *args, timeout=None) -> RunResult: - """See :meth:`Pytester.runpytest_subprocess`.""" - return self._pytester.runpytest_subprocess(*args, timeout=timeout) - - def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: - """See :meth:`Pytester.spawn_pytest`.""" - return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout) - - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: - """See :meth:`Pytester.spawn`.""" - return self._pytester.spawn(cmd, expect_timeout=expect_timeout) - - def __repr__(self) -> str: - return f"" - - def __str__(self) -> str: - return str(self.tmpdir) - - -class LegacyTestdirPlugin: - @staticmethod - @fixture - def testdir(pytester: Pytester) -> Testdir: - """ - Identical to :fixture:`pytester`, and provides an instance whose methods return - legacy ``LEGACY_PATH`` objects instead when applicable. - - New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. - """ - return Testdir(pytester, _ispytest=True) - - -@final -@dataclasses.dataclass -class TempdirFactory: - """Backward compatibility wrapper that implements ``py.path.local`` - for :class:`TempPathFactory`. - - .. note:: - These days, it is preferred to use ``tmp_path_factory``. - - :ref:`About the tmpdir and tmpdir_factory fixtures`. - - """ - - _tmppath_factory: TempPathFactory - - def __init__( - self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False - ) -> None: - check_ispytest(_ispytest) - self._tmppath_factory = tmppath_factory - - def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" - return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) - - def getbasetemp(self) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" - return legacy_path(self._tmppath_factory.getbasetemp().resolve()) - - -class LegacyTmpdirPlugin: - @staticmethod - @fixture(scope="session") - def tmpdir_factory(request: FixtureRequest) -> TempdirFactory: - """Return a :class:`pytest.TempdirFactory` instance for the test session.""" - # Set dynamically by pytest_configure(). - return request.config._tmpdirhandler # type: ignore - - @staticmethod - @fixture - def tmpdir(tmp_path: Path) -> LEGACY_PATH: - """Return a temporary directory (as `legacy_path`_ object) - which is unique to each test function invocation. - The temporary directory is created as a subdirectory - of the base temporary directory, with configurable retention, - as discussed in :ref:`temporary directory location and retention`. - - .. note:: - These days, it is preferred to use ``tmp_path``. - - :ref:`About the tmpdir and tmpdir_factory fixtures`. - - .. _legacy_path: https://py.readthedocs.io/en/latest/path.html - """ - return legacy_path(tmp_path) - - -def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH: - """Return a directory path object with the given name. - - Same as :func:`mkdir`, but returns a legacy py path instance. - """ - return legacy_path(self.mkdir(name)) - - -def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH: - """(deprecated) The file system path of the test module which collected this test.""" - return legacy_path(self.path) - - -def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH: - """The directory from which pytest was invoked. - - Prefer to use ``startpath`` which is a :class:`pathlib.Path`. - - :type: LEGACY_PATH - """ - return legacy_path(self.startpath) - - -def Config_invocation_dir(self: Config) -> LEGACY_PATH: - """The directory from which pytest was invoked. - - Prefer to use :attr:`invocation_params.dir `, - which is a :class:`pathlib.Path`. - - :type: LEGACY_PATH - """ - return legacy_path(str(self.invocation_params.dir)) - - -def Config_rootdir(self: Config) -> LEGACY_PATH: - """The path to the :ref:`rootdir `. - - Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. - - :type: LEGACY_PATH - """ - return legacy_path(str(self.rootpath)) - - -def Config_inifile(self: Config) -> LEGACY_PATH | None: - """The path to the :ref:`configfile `. - - Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. - - :type: Optional[LEGACY_PATH] - """ - return legacy_path(str(self.inipath)) if self.inipath else None - - -def Session_startdir(self: Session) -> LEGACY_PATH: - """The path from which pytest was invoked. - - Prefer to use ``startpath`` which is a :class:`pathlib.Path`. - - :type: LEGACY_PATH - """ - return legacy_path(self.startpath) - - -def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): - if type == "pathlist": - # TODO: This assert is probably not valid in all cases. - assert self.inipath is not None - dp = self.inipath.parent - input_values = shlex.split(value) if isinstance(value, str) else value - return [legacy_path(str(dp / x)) for x in input_values] - else: - raise ValueError(f"unknown configuration type: {type}", value) - - -def Node_fspath(self: Node) -> LEGACY_PATH: - """(deprecated) returns a legacy_path copy of self.path""" - return legacy_path(self.path) - - -def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None: - self.path = Path(value) - - -@hookimpl(tryfirst=True) -def pytest_load_initial_conftests(early_config: Config) -> None: - """Monkeypatch legacy path attributes in several classes, as early as possible.""" - mp = MonkeyPatch() - early_config.add_cleanup(mp.undo) - - # Add Cache.makedir(). - mp.setattr(Cache, "makedir", Cache_makedir, raising=False) - - # Add FixtureRequest.fspath property. - mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False) - - # Add TerminalReporter.startdir property. - mp.setattr( - TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False - ) - - # Add Config.{invocation_dir,rootdir,inifile} properties. - mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False) - mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False) - mp.setattr(Config, "inifile", property(Config_inifile), raising=False) - - # Add Session.startdir property. - mp.setattr(Session, "startdir", property(Session_startdir), raising=False) - - # Add pathlist configuration type. - mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) - - # Add Node.fspath property. - mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False) - - -@hookimpl -def pytest_configure(config: Config) -> None: - """Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed.""" - if config.pluginmanager.has_plugin("tmpdir"): - mp = MonkeyPatch() - config.add_cleanup(mp.undo) - # Create TmpdirFactory and attach it to the config object. - # - # This is to comply with existing plugins which expect the handler to be - # available at pytest_configure time, but ideally should be moved entirely - # to the tmpdir_factory session fixture. - try: - tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined] - except AttributeError: - # tmpdir plugin is blocked. - pass - else: - _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True) - mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) - - config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir") - - -@hookimpl -def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None: - # pytester is not loaded by default and is commonly loaded from a conftest, - # so checking for it in `pytest_configure` is not enough. - is_pytester = plugin is manager.get_plugin("pytester") - if is_pytester and not manager.is_registered(LegacyTestdirPlugin): - manager.register(LegacyTestdirPlugin, "legacypath-pytester") diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/logging.py b/tests/venv2/lib/python3.11/site-packages/_pytest/logging.py deleted file mode 100644 index 3204d43..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/logging.py +++ /dev/null @@ -1,975 +0,0 @@ -# mypy: allow-untyped-defs -"""Access and control log capturing.""" - -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Mapping -from collections.abc import Set as AbstractSet -from contextlib import contextmanager -from contextlib import nullcontext -from datetime import datetime -from datetime import timedelta -from datetime import timezone -import io -from io import StringIO -import logging -from logging import LogRecord -import os -from pathlib import Path -import re -from types import TracebackType -from typing import final -from typing import Generic -from typing import Literal -from typing import TYPE_CHECKING -from typing import TypeVar - -from _pytest import nodes -from _pytest._io import TerminalWriter -from _pytest.capture import CaptureManager -from _pytest.config import _strtobool -from _pytest.config import Config -from _pytest.config import create_terminal_writer -from _pytest.config import hookimpl -from _pytest.config import UsageError -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import FixtureRequest -from _pytest.main import Session -from _pytest.stash import StashKey -from _pytest.terminal import TerminalReporter - - -if TYPE_CHECKING: - logging_StreamHandler = logging.StreamHandler[StringIO] -else: - logging_StreamHandler = logging.StreamHandler - -DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" -DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" -_ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m") -caplog_handler_key = StashKey["LogCaptureHandler"]() -caplog_records_key = StashKey[dict[str, list[logging.LogRecord]]]() - - -def _remove_ansi_escape_sequences(text: str) -> str: - return _ANSI_ESCAPE_SEQ.sub("", text) - - -class DatetimeFormatter(logging.Formatter): - """A logging formatter which formats record with - :func:`datetime.datetime.strftime` formatter instead of - :func:`time.strftime` in case of microseconds in format string. - """ - - def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: - if datefmt and "%f" in datefmt: - ct = self.converter(record.created) - tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) - # Construct `datetime.datetime` object from `struct_time` - # and msecs information from `record` - # Using int() instead of round() to avoid it exceeding 1_000_000 and causing a ValueError (#11861). - dt = datetime(*ct[0:6], microsecond=int(record.msecs * 1000), tzinfo=tz) - return dt.strftime(datefmt) - # Use `logging.Formatter` for non-microsecond formats - return super().formatTime(record, datefmt) - - -class ColoredLevelFormatter(DatetimeFormatter): - """A logging formatter which colorizes the %(levelname)..s part of the - log format passed to __init__.""" - - LOGLEVEL_COLOROPTS: Mapping[int, AbstractSet[str]] = { - logging.CRITICAL: {"red"}, - logging.ERROR: {"red", "bold"}, - logging.WARNING: {"yellow"}, - logging.WARN: {"yellow"}, - logging.INFO: {"green"}, - logging.DEBUG: {"purple"}, - logging.NOTSET: set(), - } - LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*(?:\.\d+)?s)") - - def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self._terminalwriter = terminalwriter - self._original_fmt = self._style._fmt - self._level_to_fmt_mapping: dict[int, str] = {} - - for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): - self.add_color_level(level, *color_opts) - - def add_color_level(self, level: int, *color_opts: str) -> None: - """Add or update color opts for a log level. - - :param level: - Log level to apply a style to, e.g. ``logging.INFO``. - :param color_opts: - ANSI escape sequence color options. Capitalized colors indicates - background color, i.e. ``'green', 'Yellow', 'bold'`` will give bold - green text on yellow background. - - .. warning:: - This is an experimental API. - """ - assert self._fmt is not None - levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) - if not levelname_fmt_match: - return - levelname_fmt = levelname_fmt_match.group() - - formatted_levelname = levelname_fmt % {"levelname": logging.getLevelName(level)} - - # add ANSI escape sequences around the formatted levelname - color_kwargs = {name: True for name in color_opts} - colorized_formatted_levelname = self._terminalwriter.markup( - formatted_levelname, **color_kwargs - ) - self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub( - colorized_formatted_levelname, self._fmt - ) - - def format(self, record: logging.LogRecord) -> str: - fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt) - self._style._fmt = fmt - return super().format(record) - - -class PercentStyleMultiline(logging.PercentStyle): - """A logging style with special support for multiline messages. - - If the message of a record consists of multiple lines, this style - formats the message as if each line were logged separately. - """ - - def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None: - super().__init__(fmt) - self._auto_indent = self._get_auto_indent(auto_indent) - - @staticmethod - def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int: - """Determine the current auto indentation setting. - - Specify auto indent behavior (on/off/fixed) by passing in - extra={"auto_indent": [value]} to the call to logging.log() or - using a --log-auto-indent [value] command line or the - log_auto_indent [value] config option. - - Default behavior is auto-indent off. - - Using the string "True" or "on" or the boolean True as the value - turns auto indent on, using the string "False" or "off" or the - boolean False or the int 0 turns it off, and specifying a - positive integer fixes the indentation position to the value - specified. - - Any other values for the option are invalid, and will silently be - converted to the default. - - :param None|bool|int|str auto_indent_option: - User specified option for indentation from command line, config - or extra kwarg. Accepts int, bool or str. str option accepts the - same range of values as boolean config options, as well as - positive integers represented in str form. - - :returns: - Indentation value, which can be - -1 (automatically determine indentation) or - 0 (auto-indent turned off) or - >0 (explicitly set indentation position). - """ - match auto_indent_option: - case None | False: - return 0 - case True: - return -1 - case int(): - return auto_indent_option - case str(): - try: - return int(auto_indent_option) - except ValueError: - pass - try: - if _strtobool(auto_indent_option): - return -1 - except ValueError: - return 0 - return 0 - - def format(self, record: logging.LogRecord) -> str: - if "\n" in record.message: - if hasattr(record, "auto_indent"): - # Passed in from the "extra={}" kwarg on the call to logging.log(). - auto_indent = self._get_auto_indent(record.auto_indent) - else: - auto_indent = self._auto_indent - - if auto_indent: - lines = record.message.splitlines() - formatted = self._fmt % {**record.__dict__, "message": lines[0]} - - if auto_indent < 0: - indentation = _remove_ansi_escape_sequences(formatted).find( - lines[0] - ) - else: - # Optimizes logging by allowing a fixed indentation. - indentation = auto_indent - lines[0] = formatted - return ("\n" + " " * indentation).join(lines) - return self._fmt % record.__dict__ - - -def get_option_ini(config: Config, *names: str): - for name in names: - ret = config.getoption(name) # 'default' arg won't work as expected - if ret is None: - ret = config.getini(name) - if ret: - return ret - - -def pytest_addoption(parser: Parser) -> None: - """Add options to control log capturing.""" - group = parser.getgroup("logging") - - def add_option_ini(option, dest, default=None, type=None, **kwargs): - parser.addini( - dest, default=default, type=type, help="Default value for " + option - ) - group.addoption(option, dest=dest, **kwargs) - - add_option_ini( - "--log-level", - dest="log_level", - default=None, - metavar="LEVEL", - help=( - "Level of messages to catch/display." - " Not set by default, so it depends on the root/parent log handler's" - ' effective level, where it is "WARNING" by default.' - ), - ) - add_option_ini( - "--log-format", - dest="log_format", - default=DEFAULT_LOG_FORMAT, - help="Log format used by the logging module", - ) - add_option_ini( - "--log-date-format", - dest="log_date_format", - default=DEFAULT_LOG_DATE_FORMAT, - help="Log date format used by the logging module", - ) - parser.addini( - "log_cli", - default=False, - type="bool", - help='Enable log display during test run (also known as "live logging")', - ) - add_option_ini( - "--log-cli-level", dest="log_cli_level", default=None, help="CLI logging level" - ) - add_option_ini( - "--log-cli-format", - dest="log_cli_format", - default=None, - help="Log format used by the logging module", - ) - add_option_ini( - "--log-cli-date-format", - dest="log_cli_date_format", - default=None, - help="Log date format used by the logging module", - ) - add_option_ini( - "--log-file", - dest="log_file", - default=None, - help="Path to a file when logging will be written to", - ) - add_option_ini( - "--log-file-mode", - dest="log_file_mode", - default="w", - choices=["w", "a"], - help="Log file open mode", - ) - add_option_ini( - "--log-file-level", - dest="log_file_level", - default=None, - help="Log file logging level", - ) - add_option_ini( - "--log-file-format", - dest="log_file_format", - default=None, - help="Log format used by the logging module", - ) - add_option_ini( - "--log-file-date-format", - dest="log_file_date_format", - default=None, - help="Log date format used by the logging module", - ) - add_option_ini( - "--log-auto-indent", - dest="log_auto_indent", - default=None, - help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.", - ) - group.addoption( - "--log-disable", - action="append", - default=[], - dest="logger_disable", - help="Disable a logger by name. Can be passed multiple times.", - ) - - -_HandlerType = TypeVar("_HandlerType", bound=logging.Handler) - - -# Not using @contextmanager for performance reasons. -class catching_logs(Generic[_HandlerType]): - """Context manager that prepares the whole logging machinery properly.""" - - __slots__ = ("attached_loggers", "handler", "level", "orig_level") - - def __init__(self, handler: _HandlerType, level: int | None = None) -> None: - self.handler = handler - self.level = level - self.attached_loggers: list[logging.Logger] = [] - - def __enter__(self) -> _HandlerType: - root_logger = logging.getLogger() - if self.level is not None: - self.handler.setLevel(self.level) - # Attach to root logger. - root_logger.addHandler(self.handler) - self.attached_loggers.append(root_logger) - # Attach to all non-propagating loggers (won't reach root). - # Note that will miss loggers that *become* non-propagating - # after the `__enter__`. Not worth the trouble for now. - for logger in root_logger.manager.loggerDict.values(): - if ( - isinstance(logger, logging.Logger) - and not logger.propagate - and logger is not root_logger - ): - logger.addHandler(self.handler) - self.attached_loggers.append(logger) - if self.level is not None: - # Non-propagating loggers still inherit the level (unless a logger - # explicitly set level), so only do this on the root logger. - self.orig_level = root_logger.level - root_logger.setLevel(min(self.orig_level, self.level)) - return self.handler - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - root_logger = logging.getLogger() - if self.level is not None: - root_logger.setLevel(self.orig_level) - for logger in self.attached_loggers: - logger.removeHandler(self.handler) - self.attached_loggers.clear() - - -class LogCaptureHandler(logging_StreamHandler): - """A logging handler that stores log records and the log text.""" - - def __init__(self) -> None: - """Create a new log handler.""" - super().__init__(StringIO()) - self.records: list[logging.LogRecord] = [] - - def emit(self, record: logging.LogRecord) -> None: - """Keep the log records in a list in addition to the log text.""" - self.records.append(record) - super().emit(record) - - def reset(self) -> None: - self.records = [] - self.stream = StringIO() - - def clear(self) -> None: - self.records.clear() - self.stream = StringIO() - - def handleError(self, record: logging.LogRecord) -> None: - if logging.raiseExceptions: - # Fail the test if the log message is bad (emit failed). - # The default behavior of logging is to print "Logging error" - # to stderr with the call stack and some extra details. - # pytest wants to make such mistakes visible during testing. - raise # noqa: PLE0704 - - -@final -class LogCaptureFixture: - """Provides access and control of log capturing.""" - - def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: - check_ispytest(_ispytest) - self._item = item - self._initial_handler_level: int | None = None - # Dict of log name -> log level. - self._initial_logger_levels: dict[str | None, int] = {} - self._initial_disabled_logging_level: int | None = None - - def _finalize(self) -> None: - """Finalize the fixture. - - This restores the log levels and the disabled logging levels changed by :meth:`set_level`. - """ - # Restore log levels. - if self._initial_handler_level is not None: - self.handler.setLevel(self._initial_handler_level) - for logger_name, level in self._initial_logger_levels.items(): - logger = logging.getLogger(logger_name) - logger.setLevel(level) - # Disable logging at the original disabled logging level. - if self._initial_disabled_logging_level is not None: - logging.disable(self._initial_disabled_logging_level) - self._initial_disabled_logging_level = None - - @property - def handler(self) -> LogCaptureHandler: - """Get the logging handler used by the fixture.""" - return self._item.stash[caplog_handler_key] - - def get_records( - self, when: Literal["setup", "call", "teardown"] - ) -> list[logging.LogRecord]: - """Get the logging records for one of the possible test phases. - - :param when: - Which test phase to obtain the records from. - Valid values are: "setup", "call" and "teardown". - - :returns: The list of captured records at the given stage. - - .. versionadded:: 3.4 - """ - return self._item.stash[caplog_records_key].get(when, []) - - @property - def text(self) -> str: - """The formatted log text.""" - return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) - - @property - def records(self) -> list[logging.LogRecord]: - """The list of log records.""" - return self.handler.records - - @property - def record_tuples(self) -> list[tuple[str, int, str]]: - """A list of a stripped down version of log records intended - for use in assertion comparison. - - The format of the tuple is: - - (logger_name, log_level, message) - """ - return [(r.name, r.levelno, r.getMessage()) for r in self.records] - - @property - def messages(self) -> list[str]: - """A list of format-interpolated log messages. - - Unlike 'records', which contains the format string and parameters for - interpolation, log messages in this list are all interpolated. - - Unlike 'text', which contains the output from the handler, log - messages in this list are unadorned with levels, timestamps, etc, - making exact comparisons more reliable. - - Note that traceback or stack info (from :func:`logging.exception` or - the `exc_info` or `stack_info` arguments to the logging functions) is - not included, as this is added by the formatter in the handler. - - .. versionadded:: 3.7 - """ - return [r.getMessage() for r in self.records] - - def clear(self) -> None: - """Reset the list of log records and the captured log text.""" - self.handler.clear() - - def _force_enable_logging( - self, level: int | str, logger_obj: logging.Logger - ) -> int: - """Enable the desired logging level if the global level was disabled via ``logging.disabled``. - - Only enables logging levels greater than or equal to the requested ``level``. - - Does nothing if the desired ``level`` wasn't disabled. - - :param level: - The logger level caplog should capture. - All logging is enabled if a non-standard logging level string is supplied. - Valid level strings are in :data:`logging._nameToLevel`. - :param logger_obj: The logger object to check. - - :return: The original disabled logging level. - """ - original_disable_level: int = logger_obj.manager.disable - - if isinstance(level, str): - # Try to translate the level string to an int for `logging.disable()` - level = logging.getLevelName(level) # type: ignore[deprecated] - - if not isinstance(level, int): - # The level provided was not valid, so just un-disable all logging. - logging.disable(logging.NOTSET) - elif not logger_obj.isEnabledFor(level): - # Each level is `10` away from other levels. - # https://docs.python.org/3/library/logging.html#logging-levels - disable_level = max(level - 10, logging.NOTSET) - logging.disable(disable_level) - - return original_disable_level - - def set_level(self, level: int | str, logger: str | None = None) -> None: - """Set the threshold level of a logger for the duration of a test. - - Logging messages which are less severe than this level will not be captured. - - .. versionchanged:: 3.4 - The levels of the loggers changed by this function will be - restored to their initial values at the end of the test. - - Will enable the requested logging level if it was disabled via :func:`logging.disable`. - - :param level: The level. - :param logger: The logger to update. If not given, the root logger. - """ - logger_obj = logging.getLogger(logger) - # Save the original log-level to restore it during teardown. - self._initial_logger_levels.setdefault(logger, logger_obj.level) - logger_obj.setLevel(level) - if self._initial_handler_level is None: - self._initial_handler_level = self.handler.level - self.handler.setLevel(level) - initial_disabled_logging_level = self._force_enable_logging(level, logger_obj) - if self._initial_disabled_logging_level is None: - self._initial_disabled_logging_level = initial_disabled_logging_level - - @contextmanager - def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]: - """Context manager that sets the level for capturing of logs. After - the end of the 'with' statement the level is restored to its original - value. - - Will enable the requested logging level if it was disabled via :func:`logging.disable`. - - :param level: The level. - :param logger: The logger to update. If not given, the root logger. - """ - logger_obj = logging.getLogger(logger) - orig_level = logger_obj.level - logger_obj.setLevel(level) - handler_orig_level = self.handler.level - self.handler.setLevel(level) - original_disable_level = self._force_enable_logging(level, logger_obj) - try: - yield - finally: - logger_obj.setLevel(orig_level) - self.handler.setLevel(handler_orig_level) - logging.disable(original_disable_level) - - @contextmanager - def filtering(self, filter_: logging.Filter) -> Generator[None]: - """Context manager that temporarily adds the given filter to the caplog's - :meth:`handler` for the 'with' statement block, and removes that filter at the - end of the block. - - :param filter_: A custom :class:`logging.Filter` object. - - .. versionadded:: 7.5 - """ - self.handler.addFilter(filter_) - try: - yield - finally: - self.handler.removeFilter(filter_) - - -@fixture -def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture]: - """Access and control log capturing. - - Captured logs are available through the following properties/methods:: - - * caplog.messages -> list of format-interpolated log messages - * caplog.text -> string containing formatted log output - * caplog.records -> list of logging.LogRecord instances - * caplog.record_tuples -> list of (logger_name, level, message) tuples - * caplog.clear() -> clear captured records and formatted log output string - """ - result = LogCaptureFixture(request.node, _ispytest=True) - yield result - result._finalize() - - -def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None: - for setting_name in setting_names: - log_level = config.getoption(setting_name) - if log_level is None: - log_level = config.getini(setting_name) - if log_level: - break - else: - return None - - if isinstance(log_level, str): - log_level = log_level.upper() - try: - return int(getattr(logging, log_level, log_level)) - except ValueError as e: - # Python logging does not recognise this as a logging level - raise UsageError( - f"'{log_level}' is not recognized as a logging level name for " - f"'{setting_name}'. Please consider passing the " - "logging level num instead." - ) from e - - -# run after terminalreporter/capturemanager are configured -@hookimpl(trylast=True) -def pytest_configure(config: Config) -> None: - config.pluginmanager.register(LoggingPlugin(config), "logging-plugin") - - -class LoggingPlugin: - """Attaches to the logging module and captures log messages for each test.""" - - def __init__(self, config: Config) -> None: - """Create a new plugin to capture log messages. - - The formatter can be safely shared across all handlers so - create a single one for the entire test session here. - """ - self._config = config - - # Report logging. - self.formatter = self._create_formatter( - get_option_ini(config, "log_format"), - get_option_ini(config, "log_date_format"), - get_option_ini(config, "log_auto_indent"), - ) - self.log_level = get_log_level_for_setting(config, "log_level") - self.caplog_handler = LogCaptureHandler() - self.caplog_handler.setFormatter(self.formatter) - self.report_handler = LogCaptureHandler() - self.report_handler.setFormatter(self.formatter) - - # File logging. - self.log_file_level = get_log_level_for_setting( - config, "log_file_level", "log_level" - ) - log_file = get_option_ini(config, "log_file") or os.devnull - if log_file != os.devnull: - directory = os.path.dirname(os.path.abspath(log_file)) - if not os.path.isdir(directory): - os.makedirs(directory) - - self.log_file_mode = get_option_ini(config, "log_file_mode") or "w" - self.log_file_handler = _FileHandler( - log_file, mode=self.log_file_mode, encoding="UTF-8" - ) - log_file_format = get_option_ini(config, "log_file_format", "log_format") - log_file_date_format = get_option_ini( - config, "log_file_date_format", "log_date_format" - ) - - log_file_formatter = DatetimeFormatter( - log_file_format, datefmt=log_file_date_format - ) - self.log_file_handler.setFormatter(log_file_formatter) - - # CLI/live logging. - self.log_cli_level = get_log_level_for_setting( - config, "log_cli_level", "log_level" - ) - if self._log_cli_enabled(): - terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") - # Guaranteed by `_log_cli_enabled()`. - assert terminal_reporter is not None - capture_manager = config.pluginmanager.get_plugin("capturemanager") - # if capturemanager plugin is disabled, live logging still works. - self.log_cli_handler: ( - _LiveLoggingStreamHandler | _LiveLoggingNullHandler - ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) - else: - self.log_cli_handler = _LiveLoggingNullHandler() - log_cli_formatter = self._create_formatter( - get_option_ini(config, "log_cli_format", "log_format"), - get_option_ini(config, "log_cli_date_format", "log_date_format"), - get_option_ini(config, "log_auto_indent"), - ) - self.log_cli_handler.setFormatter(log_cli_formatter) - self._disable_loggers(loggers_to_disable=config.option.logger_disable) - - def _disable_loggers(self, loggers_to_disable: list[str]) -> None: - if not loggers_to_disable: - return - - for name in loggers_to_disable: - logger = logging.getLogger(name) - logger.disabled = True - - def _create_formatter(self, log_format, log_date_format, auto_indent): - # Color option doesn't exist if terminal plugin is disabled. - color = getattr(self._config.option, "color", "no") - if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search( - log_format - ): - formatter: logging.Formatter = ColoredLevelFormatter( - create_terminal_writer(self._config), log_format, log_date_format - ) - else: - formatter = DatetimeFormatter(log_format, log_date_format) - - formatter._style = PercentStyleMultiline( - formatter._style._fmt, auto_indent=auto_indent - ) - - return formatter - - def set_log_path(self, fname: str) -> None: - """Set the filename parameter for Logging.FileHandler(). - - Creates parent directory if it does not exist. - - .. warning:: - This is an experimental API. - """ - fpath = Path(fname) - - if not fpath.is_absolute(): - fpath = self._config.rootpath / fpath - - if not fpath.parent.exists(): - fpath.parent.mkdir(exist_ok=True, parents=True) - - # https://github.com/python/mypy/issues/11193 - stream: io.TextIOWrapper = fpath.open(mode=self.log_file_mode, encoding="UTF-8") # type: ignore[assignment] - old_stream = self.log_file_handler.setStream(stream) - if old_stream: - old_stream.close() - - def _log_cli_enabled(self) -> bool: - """Return whether live logging is enabled.""" - enabled = self._config.getoption( - "--log-cli-level" - ) is not None or self._config.getini("log_cli") - if not enabled: - return False - - terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter") - if terminal_reporter is None: - # terminal reporter is disabled e.g. by pytest-xdist. - return False - - return True - - @hookimpl(wrapper=True, tryfirst=True) - def pytest_sessionstart(self) -> Generator[None]: - self.log_cli_handler.set_when("sessionstart") - - with catching_logs(self.log_cli_handler, level=self.log_cli_level): - with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) - - @hookimpl(wrapper=True, tryfirst=True) - def pytest_collection(self) -> Generator[None]: - self.log_cli_handler.set_when("collection") - - with catching_logs(self.log_cli_handler, level=self.log_cli_level): - with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) - - @hookimpl(wrapper=True) - def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]: - if session.config.option.collectonly: - return (yield) - - if self._log_cli_enabled() and self._config.get_verbosity() < 1: - # The verbose flag is needed to avoid messy test progress output. - self._config.option.verbose = 1 - - with catching_logs(self.log_cli_handler, level=self.log_cli_level): - with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) # Run all the tests. - - @hookimpl - def pytest_runtest_logstart(self) -> None: - self.log_cli_handler.reset() - self.log_cli_handler.set_when("start") - - @hookimpl - def pytest_runtest_logreport(self) -> None: - self.log_cli_handler.set_when("logreport") - - @contextmanager - def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None]: - """Implement the internals of the pytest_runtest_xxx() hooks.""" - with ( - catching_logs( - self.caplog_handler, - level=self.log_level, - ) as caplog_handler, - catching_logs( - self.report_handler, - level=self.log_level, - ) as report_handler, - ): - caplog_handler.reset() - report_handler.reset() - item.stash[caplog_records_key][when] = caplog_handler.records - item.stash[caplog_handler_key] = caplog_handler - - try: - yield - finally: - log = report_handler.stream.getvalue().strip() - item.add_report_section(when, "log", log) - - @hookimpl(wrapper=True) - def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None]: - self.log_cli_handler.set_when("setup") - - empty: dict[str, list[logging.LogRecord]] = {} - item.stash[caplog_records_key] = empty - with self._runtest_for(item, "setup"): - yield - - @hookimpl(wrapper=True) - def pytest_runtest_call(self, item: nodes.Item) -> Generator[None]: - self.log_cli_handler.set_when("call") - - with self._runtest_for(item, "call"): - yield - - @hookimpl(wrapper=True) - def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None]: - self.log_cli_handler.set_when("teardown") - - try: - with self._runtest_for(item, "teardown"): - yield - finally: - del item.stash[caplog_records_key] - del item.stash[caplog_handler_key] - - @hookimpl - def pytest_runtest_logfinish(self) -> None: - self.log_cli_handler.set_when("finish") - - @hookimpl(wrapper=True, tryfirst=True) - def pytest_sessionfinish(self) -> Generator[None]: - self.log_cli_handler.set_when("sessionfinish") - - with catching_logs(self.log_cli_handler, level=self.log_cli_level): - with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) - - @hookimpl - def pytest_unconfigure(self) -> None: - # Close the FileHandler explicitly. - # (logging.shutdown might have lost the weakref?!) - self.log_file_handler.close() - - -class _FileHandler(logging.FileHandler): - """A logging FileHandler with pytest tweaks.""" - - def handleError(self, record: logging.LogRecord) -> None: - # Handled by LogCaptureHandler. - pass - - -class _LiveLoggingStreamHandler(logging_StreamHandler): - """A logging StreamHandler used by the live logging feature: it will - write a newline before the first log message in each test. - - During live logging we must also explicitly disable stdout/stderr - capturing otherwise it will get captured and won't appear in the - terminal. - """ - - # Officially stream needs to be a IO[str], but TerminalReporter - # isn't. So force it. - stream: TerminalReporter = None # type: ignore - - def __init__( - self, - terminal_reporter: TerminalReporter, - capture_manager: CaptureManager | None, - ) -> None: - super().__init__(stream=terminal_reporter) # type: ignore[arg-type] - self.capture_manager = capture_manager - self.reset() - self.set_when(None) - self._test_outcome_written = False - - def reset(self) -> None: - """Reset the handler; should be called before the start of each test.""" - self._first_record_emitted = False - - def set_when(self, when: str | None) -> None: - """Prepare for the given test phase (setup/call/teardown).""" - self._when = when - self._section_name_shown = False - if when == "start": - self._test_outcome_written = False - - def emit(self, record: logging.LogRecord) -> None: - ctx_manager = ( - self.capture_manager.global_and_fixture_disabled() - if self.capture_manager - else nullcontext() - ) - with ctx_manager: - if not self._first_record_emitted: - self.stream.write("\n") - self._first_record_emitted = True - elif self._when in ("teardown", "finish"): - if not self._test_outcome_written: - self._test_outcome_written = True - self.stream.write("\n") - if not self._section_name_shown and self._when: - self.stream.section("live log " + self._when, sep="-", bold=True) - self._section_name_shown = True - super().emit(record) - - def handleError(self, record: logging.LogRecord) -> None: - # Handled by LogCaptureHandler. - pass - - -class _LiveLoggingNullHandler(logging.NullHandler): - """A logging handler used when live logging is disabled.""" - - def reset(self) -> None: - pass - - def set_when(self, when: str) -> None: - pass - - def handleError(self, record: logging.LogRecord) -> None: - # Handled by LogCaptureHandler. - pass diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/main.py b/tests/venv2/lib/python3.11/site-packages/_pytest/main.py deleted file mode 100644 index c4df4e4..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/main.py +++ /dev/null @@ -1,1215 +0,0 @@ -"""Core implementation of the testing process: init, session, runtest loop.""" - -from __future__ import annotations - -import argparse -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Sequence -from collections.abc import Set as AbstractSet -import dataclasses -import fnmatch -import functools -import importlib -import importlib.util -import os -from pathlib import Path -import sys -from typing import final -from typing import Literal -from typing import overload -from typing import TYPE_CHECKING -import warnings - -import pluggy - -from _pytest import nodes -import _pytest._code -from _pytest.config import Config -from _pytest.config import directory_arg -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config import PytestPluginManager -from _pytest.config import UsageError -from _pytest.config.argparsing import OverrideIniAction -from _pytest.config.argparsing import Parser -from _pytest.outcomes import exit -from _pytest.pathlib import absolutepath -from _pytest.pathlib import bestrelpath -from _pytest.pathlib import fnmatch_ex -from _pytest.pathlib import safe_exists -from _pytest.pathlib import samefile_nofollow -from _pytest.pathlib import scandir -from _pytest.reports import CollectReport -from _pytest.reports import TestReport -from _pytest.runner import collect_one_node -from _pytest.runner import SetupState -from _pytest.warning_types import PytestWarning - - -if TYPE_CHECKING: - from typing_extensions import Self - - from _pytest.fixtures import FixtureManager - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group._addoption( # private to use reserved lower-case short option - "-x", - "--exitfirst", - action="store_const", - dest="maxfail", - const=1, - help="Exit instantly on first error or failed test", - ) - group.addoption( - "--maxfail", - metavar="num", - action="store", - type=int, - dest="maxfail", - default=0, - help="Exit after first num failures or errors", - ) - group.addoption( - "--strict-config", - action=OverrideIniAction, - ini_option="strict_config", - ini_value="true", - help="Enables the strict_config option", - ) - group.addoption( - "--strict-markers", - action=OverrideIniAction, - ini_option="strict_markers", - ini_value="true", - help="Enables the strict_markers option", - ) - group.addoption( - "--strict", - action=OverrideIniAction, - ini_option="strict", - ini_value="true", - help="Enables the strict option", - ) - parser.addini( - "strict_config", - "Any warnings encountered while parsing the `pytest` section of the " - "configuration file raise errors", - type="bool", - # None => fallback to `strict`. - default=None, - ) - parser.addini( - "strict_markers", - "Markers not registered in the `markers` section of the configuration " - "file raise errors", - type="bool", - # None => fallback to `strict`. - default=None, - ) - parser.addini( - "strict", - "Enables all strictness options, currently: " - "strict_config, strict_markers, strict_xfail, strict_parametrization_ids", - type="bool", - default=False, - ) - - group = parser.getgroup("pytest-warnings") - group.addoption( - "-W", - "--pythonwarnings", - action="append", - help="Set which warnings to report, see -W option of Python itself", - ) - group.addoption( - "--max-warnings", - action="store", - type=int, - default=None, - metavar="num", - dest="max_warnings", - help="Exit with error if all tests pass but the number of warnings exceeds this threshold", - ) - parser.addini( - "filterwarnings", - type="linelist", - help="Each line specifies a pattern for " - "warnings.filterwarnings. " - "Processed after -W/--pythonwarnings.", - ) - parser.addini( - "max_warnings", - help="Exit with error if all tests pass but the number of warnings exceeds this threshold", - ) - - group = parser.getgroup("collect", "collection") - group.addoption( - "--collectonly", - "--collect-only", - "--co", - action="store_true", - help="Only collect tests, don't execute them", - ) - group.addoption( - "--pyargs", - action="store_true", - help="Try to interpret all arguments as Python packages", - ) - group.addoption( - "--ignore", - action="append", - metavar="path", - help="Ignore path during collection (multi-allowed)", - ) - group.addoption( - "--ignore-glob", - action="append", - metavar="path", - help="Ignore path pattern during collection (multi-allowed)", - ) - group.addoption( - "--deselect", - action="append", - metavar="nodeid_prefix", - help="Deselect item (via node id prefix) during collection (multi-allowed)", - ) - group.addoption( - "--confcutdir", - dest="confcutdir", - default=None, - metavar="dir", - type=functools.partial(directory_arg, optname="--confcutdir"), - help="Only load conftest.py's relative to specified dir", - ) - group.addoption( - "--noconftest", - action="store_true", - dest="noconftest", - default=False, - help="Don't load any conftest.py files", - ) - group.addoption( - "--keepduplicates", - "--keep-duplicates", - action="store_true", - dest="keepduplicates", - default=False, - help="Keep duplicate tests", - ) - group.addoption( - "--collect-in-virtualenv", - action="store_true", - dest="collect_in_virtualenv", - default=False, - help="Don't ignore tests in a local virtualenv directory", - ) - group.addoption( - "--continue-on-collection-errors", - action="store_true", - default=False, - dest="continue_on_collection_errors", - help="Force test execution even if collection errors occur", - ) - group.addoption( - "--import-mode", - default="prepend", - choices=["prepend", "append", "importlib"], - dest="importmode", - help="Prepend/append to sys.path when importing test modules and conftest " - "files. Default: prepend.", - ) - parser.addini( - "norecursedirs", - "Directory patterns to avoid for recursion", - type="args", - default=[ - "*.egg", - ".*", - "_darcs", - "build", - "CVS", - "dist", - "node_modules", - "venv", - "{arch}", - ], - ) - parser.addini( - "testpaths", - "Directories to search for tests when no files or directories are given on the " - "command line", - type="args", - default=[], - ) - parser.addini( - "collect_imported_tests", - "Whether to collect tests in imported modules outside `testpaths`", - type="bool", - default=True, - ) - parser.addini( - "consider_namespace_packages", - type="bool", - default=False, - help="Consider namespace packages when resolving module names during import", - ) - - group = parser.getgroup("debugconfig", "test session debugging and configuration") - group._addoption( # private to use reserved lower-case short option - "-c", - "--config-file", - metavar="FILE", - type=str, - dest="inifilename", - help="Load configuration from `FILE` instead of trying to locate one of the " - "implicit configuration files.", - ) - group.addoption( - "--rootdir", - action="store", - dest="rootdir", - help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', " - "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: " - "'$HOME/root_dir'.", - ) - group.addoption( - "--basetemp", - dest="basetemp", - default=None, - type=validate_basetemp, - metavar="dir", - help=( - "Base temporary directory for this test run. " - "(Warning: this directory is removed if it exists.)" - ), - ) - - -def validate_basetemp(path: str) -> str: - # GH 7119 - msg = "basetemp must not be empty, the current working directory or any parent directory of it" - - # empty path - if not path: - raise argparse.ArgumentTypeError(msg) - - def is_ancestor(base: Path, query: Path) -> bool: - """Return whether query is an ancestor of base.""" - if base == query: - return True - return query in base.parents - - # check if path is an ancestor of cwd - if is_ancestor(Path.cwd(), Path(path).absolute()): - raise argparse.ArgumentTypeError(msg) - - # check symlinks for ancestors - if is_ancestor(Path.cwd().resolve(), Path(path).resolve()): - raise argparse.ArgumentTypeError(msg) - - return path - - -def wrap_session( - config: Config, doit: Callable[[Config, Session], int | ExitCode | None] -) -> int | ExitCode: - """Skeleton command line program.""" - session = Session.from_config(config) - session.exitstatus = ExitCode.OK - initstate = 0 - try: - try: - config._do_configure() - initstate = 1 - config.hook.pytest_sessionstart(session=session) - initstate = 2 - session.exitstatus = doit(config, session) or 0 - except UsageError: - session.exitstatus = ExitCode.USAGE_ERROR - raise - except Failed: - session.exitstatus = ExitCode.TESTS_FAILED - except (KeyboardInterrupt, exit.Exception): - excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus: int | ExitCode = ExitCode.INTERRUPTED - if isinstance(excinfo.value, exit.Exception): - if excinfo.value.returncode is not None: - exitstatus = excinfo.value.returncode - if initstate < 2: - sys.stderr.write(f"{excinfo.typename}: {excinfo.value.msg}\n") - config.hook.pytest_keyboard_interrupt(excinfo=excinfo) - session.exitstatus = exitstatus - except BaseException: - session.exitstatus = ExitCode.INTERNAL_ERROR - excinfo = _pytest._code.ExceptionInfo.from_current() - try: - config.notify_exception(excinfo, config.option) - except exit.Exception as exc: - if exc.returncode is not None: - session.exitstatus = exc.returncode - sys.stderr.write(f"{type(exc).__name__}: {exc}\n") - else: - if isinstance(excinfo.value, SystemExit): - sys.stderr.write("mainloop: caught unexpected SystemExit!\n") - - finally: - # Explicitly break reference cycle. - excinfo = None # type: ignore - os.chdir(session.startpath) - if initstate >= 2: - try: - config.hook.pytest_sessionfinish( - session=session, exitstatus=session.exitstatus - ) - except exit.Exception as exc: - if exc.returncode is not None: - session.exitstatus = exc.returncode - sys.stderr.write(f"{type(exc).__name__}: {exc}\n") - config._ensure_unconfigure() - return session.exitstatus - - -def pytest_cmdline_main(config: Config) -> int | ExitCode: - return wrap_session(config, _main) - - -def _main(config: Config, session: Session) -> int | ExitCode | None: - """Default command line protocol for initialization, session, - running tests and reporting.""" - config.hook.pytest_collection(session=session) - config.hook.pytest_runtestloop(session=session) - - if session.testsfailed: - return ExitCode.TESTS_FAILED - elif session.testscollected == 0: - return ExitCode.NO_TESTS_COLLECTED - return None - - -def pytest_collection(session: Session) -> None: - session.perform_collect() - - -def pytest_runtestloop(session: Session) -> bool: - if session.testsfailed and not session.config.option.continue_on_collection_errors: - raise session.Interrupted( - f"{session.testsfailed} error{'s' if session.testsfailed != 1 else ''} during collection" - ) - - if session.config.option.collectonly: - return True - - for i, item in enumerate(session.items): - nextitem = session.items[i + 1] if i + 1 < len(session.items) else None - item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) - if session.shouldfail: - raise session.Failed(session.shouldfail) - if session.shouldstop: - raise session.Interrupted(session.shouldstop) - return True - - -def _in_venv(path: Path) -> bool: - """Attempt to detect if ``path`` is the root of a Virtual Environment by - checking for the existence of the pyvenv.cfg file. - - [https://peps.python.org/pep-0405/] - - For regression protection we also check for conda environments that do not include pyenv.cfg yet -- - https://github.com/conda/conda/issues/13337 is the conda issue tracking adding pyenv.cfg. - - Checking for the `conda-meta/history` file per https://github.com/pytest-dev/pytest/issues/12652#issuecomment-2246336902. - - """ - try: - return ( - path.joinpath("pyvenv.cfg").is_file() - or path.joinpath("conda-meta", "history").is_file() - ) - except OSError: - return False - - -def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: - if collection_path.name == "__pycache__": - return True - - ignore_paths = config._getconftest_pathlist( - "collect_ignore", path=collection_path.parent - ) - ignore_paths = ignore_paths or [] - excludeopt = config.getoption("ignore") - if excludeopt: - ignore_paths.extend(absolutepath(x) for x in excludeopt) - - if collection_path in ignore_paths: - return True - - ignore_globs = config._getconftest_pathlist( - "collect_ignore_glob", path=collection_path.parent - ) - ignore_globs = ignore_globs or [] - excludeglobopt = config.getoption("ignore_glob") - if excludeglobopt: - ignore_globs.extend(absolutepath(x) for x in excludeglobopt) - - if any(fnmatch.fnmatch(str(collection_path), str(glob)) for glob in ignore_globs): - return True - - allow_in_venv = config.getoption("collect_in_virtualenv") - if not allow_in_venv and _in_venv(collection_path): - return True - - if collection_path.is_dir(): - norecursepatterns = config.getini("norecursedirs") - if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns): - return True - - return None - - -def pytest_collect_directory( - path: Path, parent: nodes.Collector -) -> nodes.Collector | None: - return Dir.from_parent(parent, path=path) - - -def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: - deselect_prefixes = tuple(config.getoption("deselect") or []) - if not deselect_prefixes: - return - - remaining = [] - deselected = [] - for colitem in items: - if colitem.nodeid.startswith(deselect_prefixes): - deselected.append(colitem) - else: - remaining.append(colitem) - - if deselected: - config.hook.pytest_deselected(items=deselected) - items[:] = remaining - - -class FSHookProxy: - def __init__( - self, - pm: PytestPluginManager, - remove_mods: AbstractSet[object], - ) -> None: - self.pm = pm - self.remove_mods = remove_mods - - def __getattr__(self, name: str) -> pluggy.HookCaller: - x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods) - self.__dict__[name] = x - return x - - -class Interrupted(KeyboardInterrupt): - """Signals that the test run was interrupted.""" - - __module__ = "builtins" # For py3. - - -class Failed(Exception): - """Signals a stop as failed test run.""" - - -@dataclasses.dataclass -class _bestrelpath_cache(dict[Path, str]): - __slots__ = ("path",) - - path: Path - - def __missing__(self, path: Path) -> str: - r = bestrelpath(self.path, path) - self[path] = r - return r - - -@final -class Dir(nodes.Directory): - """Collector of files in a file system directory. - - .. versionadded:: 8.0 - - .. note:: - - Python directories with an `__init__.py` file are instead collected by - :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory` - collectors. - """ - - @classmethod - def from_parent( # type: ignore[override] - cls, - parent: nodes.Collector, - *, - path: Path, - ) -> Self: - """The public constructor. - - :param parent: The parent collector of this Dir. - :param path: The directory's path. - :type path: pathlib.Path - """ - return super().from_parent(parent=parent, path=path) - - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - config = self.config - col: nodes.Collector | None - cols: Sequence[nodes.Collector] - ihook = self.ihook - for direntry in scandir(self.path): - if direntry.is_dir(): - path = Path(direntry.path) - if not self.session.isinitpath(path, with_parents=True): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - col = ihook.pytest_collect_directory(path=path, parent=self) - if col is not None: - yield col - - elif direntry.is_file(): - path = Path(direntry.path) - if not self.session.isinitpath(path): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - cols = ihook.pytest_collect_file(file_path=path, parent=self) - yield from cols - - -@final -class Session(nodes.Collector): - """The root of the collection tree. - - ``Session`` collects the initial paths given as arguments to pytest. - """ - - Interrupted = Interrupted - Failed = Failed - # Set on the session by runner.pytest_sessionstart. - _setupstate: SetupState - # Set on the session by fixtures.pytest_sessionstart. - _fixturemanager: FixtureManager - exitstatus: int | ExitCode - - def __init__(self, config: Config) -> None: - super().__init__( - name="", - path=config.rootpath, - fspath=None, - parent=None, - config=config, - session=self, - nodeid="", - ) - self.testsfailed = 0 - self.testscollected = 0 - self._shouldstop: bool | str = False - self._shouldfail: bool | str = False - self.trace = config.trace.root.get("collection") - self._initialpaths: frozenset[Path] = frozenset() - self._initialpaths_with_parents: frozenset[Path] = frozenset() - self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: list[CollectionArgument] = [] - self._collection_cache: dict[nodes.Collector, CollectReport] = {} - self.items: list[nodes.Item] = [] - - self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) - - self.config.pluginmanager.register(self, name="session") - - @classmethod - def from_config(cls, config: Config) -> Session: - session: Session = cls._create(config=config) - return session - - def __repr__(self) -> str: - return ( - f"<{self.__class__.__name__} {self.name} " - f"exitstatus=%r " - f"testsfailed={self.testsfailed} " - f"testscollected={self.testscollected}>" - ) % getattr(self, "exitstatus", "") - - @property - def shouldstop(self) -> bool | str: - return self._shouldstop - - @shouldstop.setter - def shouldstop(self, value: bool | str) -> None: - # The runner checks shouldfail and assumes that if it is set we are - # definitely stopping, so prevent unsetting it. - if value is False and self._shouldstop: - warnings.warn( - PytestWarning( - "session.shouldstop cannot be unset after it has been set; ignoring." - ), - stacklevel=2, - ) - return - self._shouldstop = value - - @property - def shouldfail(self) -> bool | str: - return self._shouldfail - - @shouldfail.setter - def shouldfail(self, value: bool | str) -> None: - # The runner checks shouldfail and assumes that if it is set we are - # definitely stopping, so prevent unsetting it. - if value is False and self._shouldfail: - warnings.warn( - PytestWarning( - "session.shouldfail cannot be unset after it has been set; ignoring." - ), - stacklevel=2, - ) - return - self._shouldfail = value - - @property - def startpath(self) -> Path: - """The path from which pytest was invoked. - - .. versionadded:: 7.0.0 - """ - return self.config.invocation_params.dir - - def _node_location_to_relpath(self, node_path: Path) -> str: - # bestrelpath is a quite slow function. - return self._bestrelpathcache[node_path] - - @hookimpl(tryfirst=True) - def pytest_collectstart(self) -> None: - if self.shouldfail: - raise self.Failed(self.shouldfail) - if self.shouldstop: - raise self.Interrupted(self.shouldstop) - - @hookimpl(tryfirst=True) - def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: - if report.failed and not hasattr(report, "wasxfail"): - self.testsfailed += 1 - maxfail = self.config.getvalue("maxfail") - if maxfail and self.testsfailed >= maxfail: - self.shouldfail = f"stopping after {self.testsfailed} failures" - - pytest_collectreport = pytest_runtest_logreport - - def isinitpath( - self, - path: str | os.PathLike[str], - *, - with_parents: bool = False, - ) -> bool: - """Is path an initial path? - - An initial path is a path explicitly given to pytest on the command - line. - - :param with_parents: - If set, also return True if the path is a parent of an initial path. - - .. versionchanged:: 8.0 - Added the ``with_parents`` parameter. - """ - # Optimization: Path(Path(...)) is much slower than isinstance. - path_ = path if isinstance(path, Path) else Path(path) - if with_parents: - return path_ in self._initialpaths_with_parents - else: - return path_ in self._initialpaths - - def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: - # Optimization: Path(Path(...)) is much slower than isinstance. - path = fspath if isinstance(fspath, Path) else Path(fspath) - pm = self.config.pluginmanager - # Check if we have the common case of running - # hooks with all conftest.py files. - my_conftestmodules = pm._getconftestmodules(path) - remove_mods = pm._conftest_plugins.difference(my_conftestmodules) - proxy: pluggy.HookRelay - if remove_mods: - # One or more conftests are not in use at this path. - proxy = FSHookProxy(pm, remove_mods) # type: ignore[assignment] - else: - # All plugins are active for this fspath. - proxy = self.config.hook - return proxy - - def _collect_path( - self, - path: Path, - path_cache: dict[Path, Sequence[nodes.Collector]], - ) -> Sequence[nodes.Collector]: - """Create a Collector for the given path. - - `path_cache` makes it so the same Collectors are returned for the same - path. - """ - if path in path_cache: - return path_cache[path] - - if path.is_dir(): - ihook = self.gethookproxy(path.parent) - col: nodes.Collector | None = ihook.pytest_collect_directory( - path=path, parent=self - ) - cols: Sequence[nodes.Collector] = (col,) if col is not None else () - - elif path.is_file(): - ihook = self.gethookproxy(path) - cols = ihook.pytest_collect_file(file_path=path, parent=self) - - else: - # Broken symlink or invalid/missing file. - cols = () - - path_cache[path] = cols - return cols - - @overload - def perform_collect( - self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... - ) -> Sequence[nodes.Item]: ... - - @overload - def perform_collect( - self, args: Sequence[str] | None = ..., genitems: bool = ... - ) -> Sequence[nodes.Item | nodes.Collector]: ... - - def perform_collect( - self, args: Sequence[str] | None = None, genitems: bool = True - ) -> Sequence[nodes.Item | nodes.Collector]: - """Perform the collection phase for this session. - - This is called by the default :hook:`pytest_collection` hook - implementation; see the documentation of this hook for more details. - For testing purposes, it may also be called directly on a fresh - ``Session``. - - This function normally recursively expands any collectors collected - from the session to their items, and only items are returned. For - testing purposes, this may be suppressed by passing ``genitems=False``, - in which case the return value contains these collectors unexpanded, - and ``session.items`` is empty. - """ - if args is None: - args = self.config.args - - self.trace("perform_collect", self, args) - self.trace.root.indent += 1 - - hook = self.config.hook - - self._notfound = [] - self._initial_parts = [] - self._collection_cache = {} - self.items = [] - items: Sequence[nodes.Item | nodes.Collector] = self.items - consider_namespace_packages: bool = self.config.getini( - "consider_namespace_packages" - ) - try: - initialpaths: list[Path] = [] - initialpaths_with_parents: list[Path] = [] - - collection_args = [ - resolve_collection_argument( - self.config.invocation_params.dir, - arg, - i, - as_pypath=self.config.option.pyargs, - consider_namespace_packages=consider_namespace_packages, - ) - for i, arg in enumerate(args) - ] - - if not self.config.getoption("keepduplicates"): - # Normalize the collection arguments -- remove duplicates and overlaps. - self._initial_parts = normalize_collection_arguments(collection_args) - else: - self._initial_parts = collection_args - - for collection_argument in self._initial_parts: - initialpaths.append(collection_argument.path) - initialpaths_with_parents.append(collection_argument.path) - initialpaths_with_parents.extend(collection_argument.path.parents) - self._initialpaths = frozenset(initialpaths) - self._initialpaths_with_parents = frozenset(initialpaths_with_parents) - - rep = collect_one_node(self) - self.ihook.pytest_collectreport(report=rep) - self.trace.root.indent -= 1 - if self._notfound: - errors = [] - for arg, collectors in self._notfound: - if collectors: - errors.append( - f"not found: {arg}\n(no match in any of {collectors!r})" - ) - else: - errors.append(f"found no collectors for {arg}") - - raise UsageError(*errors) - - if not genitems: - items = rep.result - else: - if rep.passed: - for node in rep.result: - self.items.extend(self.genitems(node)) - - self.config.pluginmanager.check_pending() - hook.pytest_collection_modifyitems( - session=self, config=self.config, items=items - ) - finally: - self._notfound = [] - self._initial_parts = [] - self._collection_cache = {} - hook.pytest_collection_finish(session=self) - - if genitems: - self.testscollected = len(items) - - return items - - def _collect_one_node( - self, - node: nodes.Collector, - handle_dupes: bool = True, - ) -> tuple[CollectReport, bool]: - if node in self._collection_cache and handle_dupes: - rep = self._collection_cache[node] - return rep, True - else: - rep = collect_one_node(node) - self._collection_cache[node] = rep - return rep, False - - def collect(self) -> Iterator[nodes.Item | nodes.Collector]: - # This is a cache for the root directories of the initial paths. - # We can't use collection_cache for Session because of its special - # role as the bootstrapping collector. - path_cache: dict[Path, Sequence[nodes.Collector]] = {} - - pm = self.config.pluginmanager - - for collection_argument in self._initial_parts: - self.trace("processing argument", collection_argument) - self.trace.root.indent += 1 - - argpath = collection_argument.path - names = collection_argument.parts - parametrization = collection_argument.parametrization - module_name = collection_argument.module_name - - # resolve_collection_argument() ensures this. - if argpath.is_dir(): - assert not names, f"invalid arg {(argpath, names)!r}" - - paths = [argpath] - # Add relevant parents of the path, from the root, e.g. - # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] - if module_name is None: - # Paths outside of the confcutdir should not be considered. - for path in argpath.parents: - if not pm._is_in_confcutdir(path): - break - paths.insert(0, path) - else: - # For --pyargs arguments, only consider paths matching the module - # name. Paths beyond the package hierarchy are not included. - module_name_parts = module_name.split(".") - for i, path in enumerate(argpath.parents, 2): - if i > len(module_name_parts) or path.stem != module_name_parts[-i]: - break - paths.insert(0, path) - - # Start going over the parts from the root, collecting each level - # and discarding all nodes which don't match the level's part. - any_matched_in_initial_part = False - notfound_collectors = [] - work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ - (self, [*paths, *names]) - ] - while work: - matchnode, matchparts = work.pop() - - # Pop'd all of the parts, this is a match. - if not matchparts: - yield matchnode - any_matched_in_initial_part = True - continue - - # Should have been matched by now, discard. - if not isinstance(matchnode, nodes.Collector): - continue - - # Collect this level of matching. - # Collecting Session (self) is done directly to avoid endless - # recursion to this function. - subnodes: Sequence[nodes.Collector | nodes.Item] - if isinstance(matchnode, Session): - assert isinstance(matchparts[0], Path) - subnodes = matchnode._collect_path(matchparts[0], path_cache) - else: - # For backward compat, files given directly multiple - # times on the command line should not be deduplicated. - handle_dupes = not ( - len(matchparts) == 1 - and isinstance(matchparts[0], Path) - and matchparts[0].is_file() - ) - rep, duplicate = self._collect_one_node(matchnode, handle_dupes) - if not duplicate and not rep.passed: - # Report collection failures here to avoid failing to - # run some test specified in the command line because - # the module could not be imported (#134). - matchnode.ihook.pytest_collectreport(report=rep) - if not rep.passed: - continue - subnodes = rep.result - - # Prune this level. - any_matched_in_collector = False - for node in reversed(subnodes): - # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. - if isinstance(matchparts[0], Path): - is_match = node.path == matchparts[0] - if sys.platform == "win32" and not is_match: - # In case the file paths do not match, fallback to samefile() to - # account for short-paths on Windows (#11895). But use a version - # which doesn't resolve symlinks, otherwise we might match the - # same file more than once (#12039). - is_match = samefile_nofollow(node.path, matchparts[0]) - - # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. - else: - if len(matchparts) == 1: - # This the last part, one parametrization goes. - if parametrization is not None: - # A parametrized arg must match exactly. - is_match = node.name == matchparts[0] + parametrization - else: - # A non-parameterized arg matches all parametrizations (if any). - # TODO: Remove the hacky split once the collection structure - # contains parametrization. - is_match = node.name.split("[")[0] == matchparts[0] - else: - is_match = node.name == matchparts[0] - if is_match: - work.append((node, matchparts[1:])) - any_matched_in_collector = True - - if not any_matched_in_collector: - notfound_collectors.append(matchnode) - - if not any_matched_in_initial_part: - report_arg = "::".join((str(argpath), *names)) - self._notfound.append((report_arg, notfound_collectors)) - - self.trace.root.indent -= 1 - - def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: - self.trace("genitems", node) - if isinstance(node, nodes.Item): - node.ihook.pytest_itemcollected(item=node) - yield node - else: - assert isinstance(node, nodes.Collector) - # For backward compat, dedup only applies to files. - handle_dupes = not isinstance(node, nodes.File) - rep, duplicate = self._collect_one_node(node, handle_dupes) - if rep.passed: - for subnode in rep.result: - yield from self.genitems(subnode) - if not duplicate: - node.ihook.pytest_collectreport(report=rep) - - -def search_pypath( - module_name: str, *, consider_namespace_packages: bool = False -) -> str | None: - """Search sys.path for the given a dotted module name, and return its file - system path if found.""" - try: - spec = importlib.util.find_spec(module_name) - # AttributeError: looks like package module, but actually filename - # ImportError: module does not exist - # ValueError: not a module name - except (AttributeError, ImportError, ValueError): - return None - - if spec is None: - return None - - if ( - spec.submodule_search_locations is None - or len(spec.submodule_search_locations) == 0 - ): - # Must be a simple module. - return spec.origin - - if consider_namespace_packages: - # If submodule_search_locations is set, it's a package (regular or namespace). - # Typically there is a single entry, but documentation claims it can be empty too - # (e.g. if the package has no physical location). - return spec.submodule_search_locations[0] - - if spec.origin is None: - # This is only the case for namespace packages - return None - - return os.path.dirname(spec.origin) - - -@dataclasses.dataclass(frozen=True) -class CollectionArgument: - """A resolved collection argument.""" - - path: Path - parts: Sequence[str] - parametrization: str | None - module_name: str | None - original_index: int - - -def resolve_collection_argument( - invocation_path: Path, - arg: str, - arg_index: int, - *, - as_pypath: bool = False, - consider_namespace_packages: bool = False, -) -> CollectionArgument: - """Parse path arguments optionally containing selection parts and return (fspath, names). - - Command-line arguments can point to files and/or directories, and optionally contain - parts for specific tests selection, for example: - - "pkg/tests/test_foo.py::TestClass::test_foo" - - This function ensures the path exists, and returns a resolved `CollectionArgument`: - - CollectionArgument( - path=Path("/full/path/to/pkg/tests/test_foo.py"), - parts=["TestClass", "test_foo"], - module_name=None, - ) - - When as_pypath is True, expects that the command-line argument actually contains - module paths instead of file-system paths: - - "pkg.tests.test_foo::TestClass::test_foo[a,b]" - - In which case we search sys.path for a matching module, and then return the *path* to the - found module, which may look like this: - - CollectionArgument( - path=Path("/home/u/myvenv/lib/site-packages/pkg/tests/test_foo.py"), - parts=["TestClass", "test_foo"], - parametrization="[a,b]", - module_name="pkg.tests.test_foo", - ) - - If the path doesn't exist, raise UsageError. - If the path is a directory and selection parts are present, raise UsageError. - """ - base, squacket, rest = arg.partition("[") - strpath, *parts = base.split("::") - if squacket and not parts: - raise UsageError(f"path cannot contain [] parametrization: {arg}") - parametrization = f"{squacket}{rest}" if squacket else None - module_name = None - if as_pypath: - pyarg_strpath = search_pypath( - strpath, consider_namespace_packages=consider_namespace_packages - ) - if pyarg_strpath is not None: - module_name = strpath - strpath = pyarg_strpath - fspath = invocation_path / strpath - fspath = absolutepath(fspath) - if not safe_exists(fspath): - msg = ( - "module or package not found: {arg} (missing __init__.py?)" - if as_pypath - else "file or directory not found: {arg}" - ) - raise UsageError(msg.format(arg=arg)) - if parts and fspath.is_dir(): - msg = ( - "package argument cannot contain :: selection parts: {arg}" - if as_pypath - else "directory argument cannot contain :: selection parts: {arg}" - ) - raise UsageError(msg.format(arg=arg)) - return CollectionArgument( - path=fspath, - parts=parts, - parametrization=parametrization, - module_name=module_name, - original_index=arg_index, - ) - - -def is_collection_argument_subsumed_by( - arg: CollectionArgument, by: CollectionArgument -) -> bool: - """Check if `arg` is subsumed (contained) by `by`.""" - # First check path subsumption. - if by.path != arg.path: - # `by` subsumes `arg` if `by` is a parent directory of `arg` and has no - # parts (collects everything in that directory). - if not by.parts: - return arg.path.is_relative_to(by.path) - return False - # Paths are equal, check parts. - # For example: ("TestClass",) is a prefix of ("TestClass", "test_method"). - if len(by.parts) > len(arg.parts) or arg.parts[: len(by.parts)] != by.parts: - return False - # Paths and parts are equal, check parametrization. - # A `by` without parametrization (None) matches everything, e.g. - # `pytest x.py::test_it` matches `x.py::test_it[0]`. Otherwise must be - # exactly equal. - if by.parametrization is not None and by.parametrization != arg.parametrization: - return False - return True - - -def normalize_collection_arguments( - collection_args: Sequence[CollectionArgument], -) -> list[CollectionArgument]: - """Normalize collection arguments to eliminate overlapping paths and parts. - - Detects when collection arguments overlap in either paths or parts and only - keeps the shorter prefix, or the earliest argument if duplicate, preserving - order. The result is prefix-free. - """ - # A quadratic algorithm is not acceptable since large inputs are possible. - # So this uses an O(n*log(n)) algorithm which takes advantage of the - # property that after sorting, a collection argument will immediately - # precede collection arguments it subsumes. An O(n) algorithm is not worth - # it. - collection_args_sorted = sorted( - collection_args, - key=lambda arg: (arg.path, arg.parts, arg.parametrization or ""), - ) - normalized: list[CollectionArgument] = [] - last_kept = None - for arg in collection_args_sorted: - if last_kept is None or not is_collection_argument_subsumed_by(arg, last_kept): - normalized.append(arg) - last_kept = arg - normalized.sort(key=lambda arg: arg.original_index) - return normalized diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__init__.py b/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__init__.py deleted file mode 100644 index 56c407a..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__init__.py +++ /dev/null @@ -1,301 +0,0 @@ -"""Generic mechanism for marking and selecting python functions.""" - -from __future__ import annotations - -import collections -from collections.abc import Collection -from collections.abc import Iterable -from collections.abc import Set as AbstractSet -import dataclasses -from typing import TYPE_CHECKING - -from .expression import Expression -from .structures import _HiddenParam -from .structures import EMPTY_PARAMETERSET_OPTION -from .structures import get_empty_parameterset_mark -from .structures import HIDDEN_PARAM -from .structures import Mark -from .structures import MARK_GEN -from .structures import MarkDecorator -from .structures import MarkGenerator -from .structures import ParameterSet -from _pytest.compat import NOTSET -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config import UsageError -from _pytest.config.argparsing import Parser -from _pytest.stash import StashKey - - -if TYPE_CHECKING: - from _pytest.nodes import Item - - -__all__ = [ - "HIDDEN_PARAM", - "MARK_GEN", - "Mark", - "MarkDecorator", - "MarkGenerator", - "ParameterSet", - "get_empty_parameterset_mark", -] - - -old_mark_config_key = StashKey[Config | None]() - - -def param( - *values: object, - marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), - id: str | _HiddenParam | None = None, -) -> ParameterSet: - """Specify a parameter in `pytest.mark.parametrize`_ calls or - :ref:`parametrized fixtures `. - - .. code-block:: python - - @pytest.mark.parametrize( - "test_input,expected", - [ - ("3+5", 8), - pytest.param("6*9", 42, marks=pytest.mark.xfail), - ], - ) - def test_eval(test_input, expected): - assert eval(test_input) == expected - - :param values: Variable args of the values of the parameter set, in order. - - :param marks: - A single mark or a list of marks to be applied to this parameter set. - - :ref:`pytest.mark.usefixtures ` cannot be added via this parameter. - - :type id: str | Literal[pytest.HIDDEN_PARAM] | None - :param id: - The id to attribute to this parameter set. - - .. versionadded:: 8.4 - :ref:`hidden-param` means to hide the parameter set - from the test name. Can only be used at most 1 time, as - test names need to be unique. - """ - return ParameterSet.param(*values, marks=marks, id=id) - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group._addoption( # private to use reserved lower-case short option - "-k", - action="store", - dest="keyword", - default="", - metavar="EXPRESSION", - help="Only run tests which match the given substring expression. " - "An expression is a Python evaluable expression " - "where all names are substring-matched against test names " - "and their parent classes. Example: -k 'test_method or test_" - "other' matches all test functions and classes whose name " - "contains 'test_method' or 'test_other', while -k 'not test_method' " - "matches those that don't contain 'test_method' in their names. " - "-k 'not test_method and not test_other' will eliminate the matches. " - "Additionally keywords are matched to classes and functions " - "containing extra names in their 'extra_keyword_matches' set, " - "as well as functions which have names assigned directly to them. " - "The matching is case-insensitive.", - ) - - group._addoption( # private to use reserved lower-case short option - "-m", - action="store", - dest="markexpr", - default="", - metavar="MARKEXPR", - help="Only run tests matching given mark expression. " - "For example: -m 'mark1 and not mark2'.", - ) - - group.addoption( - "--markers", - action="store_true", - help="show markers (builtin, plugin and per-project ones).", - ) - - parser.addini("markers", "Register new markers for test functions", "linelist") - parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") - - -@hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - import _pytest.config - - if config.option.markers: - config._do_configure() - tw = _pytest.config.create_terminal_writer(config) - for line in config.getini("markers"): - parts = line.split(":", 1) - name = parts[0] - rest = parts[1] if len(parts) == 2 else "" - tw.write(f"@pytest.mark.{name}:", bold=True) - tw.line(rest) - tw.line() - config._ensure_unconfigure() - return 0 - - return None - - -@dataclasses.dataclass -class KeywordMatcher: - """A matcher for keywords. - - Given a list of names, matches any substring of one of these names. The - string inclusion check is case-insensitive. - - Will match on the name of colitem, including the names of its parents. - Only matches names of items which are either a :class:`Class` or a - :class:`Function`. - - Additionally, matches on names in the 'extra_keyword_matches' set of - any item, as well as names directly assigned to test functions. - """ - - __slots__ = ("_names",) - - _names: AbstractSet[str] - - @classmethod - def from_item(cls, item: Item) -> KeywordMatcher: - mapped_names = set() - - # Add the names of the current item and any parent items, - # except the Session and root Directory's which are not - # interesting for matching. - import pytest - - for node in item.listchain(): - if isinstance(node, pytest.Session): - continue - if isinstance(node, pytest.Directory) and isinstance( - node.parent, pytest.Session - ): - continue - mapped_names.add(node.name) - - # Add the names added as extra keywords to current or parent items. - mapped_names.update(item.listextrakeywords()) - - # Add the names attached to the current function through direct assignment. - function_obj = getattr(item, "function", None) - if function_obj: - mapped_names.update(function_obj.__dict__) - - # Add the markers to the keywords as we no longer handle them correctly. - mapped_names.update(mark.name for mark in item.iter_markers()) - - return cls(mapped_names) - - def __call__(self, subname: str, /, **kwargs: str | int | bool | None) -> bool: - if kwargs: - raise UsageError("Keyword expressions do not support call parameters.") - subname = subname.lower() - return any(subname in name.lower() for name in self._names) - - -def deselect_by_keyword(items: list[Item], config: Config) -> None: - keywordexpr = config.option.keyword.lstrip() - if not keywordexpr: - return - - expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'") - - remaining = [] - deselected = [] - for colitem in items: - if not expr.evaluate(KeywordMatcher.from_item(colitem)): - deselected.append(colitem) - else: - remaining.append(colitem) - - if deselected: - config.hook.pytest_deselected(items=deselected) - items[:] = remaining - - -@dataclasses.dataclass -class MarkMatcher: - """A matcher for markers which are present. - - Tries to match on any marker names, attached to the given colitem. - """ - - __slots__ = ("own_mark_name_mapping",) - - own_mark_name_mapping: dict[str, list[Mark]] - - @classmethod - def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher: - mark_name_mapping = collections.defaultdict(list) - for mark in markers: - mark_name_mapping[mark.name].append(mark) - return cls(mark_name_mapping) - - def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: - if not (matches := self.own_mark_name_mapping.get(name, [])): - return False - - for mark in matches: # pylint: disable=consider-using-any-or-all - if all(mark.kwargs.get(k, NOTSET) == v for k, v in kwargs.items()): - return True - return False - - -def deselect_by_mark(items: list[Item], config: Config) -> None: - matchexpr = config.option.markexpr - if not matchexpr: - return - - expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") - remaining: list[Item] = [] - deselected: list[Item] = [] - for item in items: - if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())): - remaining.append(item) - else: - deselected.append(item) - if deselected: - config.hook.pytest_deselected(items=deselected) - items[:] = remaining - - -def _parse_expression(expr: str, exc_message: str) -> Expression: - try: - return Expression.compile(expr) - except SyntaxError as e: - raise UsageError( - f"{exc_message}: {e.text}: at column {e.offset}: {e.msg}" - ) from None - - -def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: - deselect_by_keyword(items, config) - deselect_by_mark(items, config) - - -def pytest_configure(config: Config) -> None: - config.stash[old_mark_config_key] = MARK_GEN._config - MARK_GEN._config = config - - empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) - - if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): - raise UsageError( - f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect" - f" but it is {empty_parameterset!r}" - ) - - -def pytest_unconfigure(config: Config) -> None: - MARK_GEN._config = config.stash.get(old_mark_config_key, None) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index cd04f5c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/expression.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/expression.cpython-311.pyc deleted file mode 100644 index 1bb0365..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/expression.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/structures.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/structures.cpython-311.pyc deleted file mode 100644 index 176195c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/__pycache__/structures.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/expression.py b/tests/venv2/lib/python3.11/site-packages/_pytest/mark/expression.py deleted file mode 100644 index 4b4a68d..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/expression.py +++ /dev/null @@ -1,353 +0,0 @@ -r"""Evaluate match expressions, as used by `-k` and `-m`. - -The grammar is: - -expression: expr? EOF -expr: and_expr ('or' and_expr)* -and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident kwargs? - -ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ -kwargs: ('(' name '=' value ( ', ' name '=' value )* ')') -name: a valid ident, but not a reserved keyword -value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' - -The semantics are: - -- Empty expression evaluates to False. -- ident evaluates to True or False according to a provided matcher function. -- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function. -- or/and/not evaluate according to the usual boolean semantics. -""" - -from __future__ import annotations - -import ast -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence -import dataclasses -import enum -import keyword -import re -import types -from typing import Final -from typing import final -from typing import Literal -from typing import NoReturn -from typing import overload -from typing import Protocol - - -__all__ = [ - "Expression", - "ExpressionMatcher", -] - - -FILE_NAME: Final = "" - - -class TokenType(enum.Enum): - LPAREN = "left parenthesis" - RPAREN = "right parenthesis" - OR = "or" - AND = "and" - NOT = "not" - IDENT = "identifier" - EOF = "end of input" - EQUAL = "=" - STRING = "string literal" - COMMA = "," - - -@dataclasses.dataclass(frozen=True) -class Token: - __slots__ = ("pos", "type", "value") - type: TokenType - value: str - pos: int - - -class Scanner: - __slots__ = ("current", "input", "tokens") - - def __init__(self, input: str) -> None: - self.input = input - self.tokens = self.lex(input) - self.current = next(self.tokens) - - def lex(self, input: str) -> Iterator[Token]: - pos = 0 - while pos < len(input): - if input[pos] in (" ", "\t"): - pos += 1 - elif input[pos] == "(": - yield Token(TokenType.LPAREN, "(", pos) - pos += 1 - elif input[pos] == ")": - yield Token(TokenType.RPAREN, ")", pos) - pos += 1 - elif input[pos] == "=": - yield Token(TokenType.EQUAL, "=", pos) - pos += 1 - elif input[pos] == ",": - yield Token(TokenType.COMMA, ",", pos) - pos += 1 - elif (quote_char := input[pos]) in ("'", '"'): - end_quote_pos = input.find(quote_char, pos + 1) - if end_quote_pos == -1: - raise SyntaxError( - f'closing quote "{quote_char}" is missing', - (FILE_NAME, 1, pos + 1, input), - ) - value = input[pos : end_quote_pos + 1] - if (backslash_pos := value.find("\\")) != -1: - raise SyntaxError( - r'escaping with "\" not supported in marker expression', - (FILE_NAME, 1, pos + backslash_pos + 1, input), - ) - yield Token(TokenType.STRING, value, pos) - pos += len(value) - else: - match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:]) - if match: - value = match.group(0) - if value == "or": - yield Token(TokenType.OR, value, pos) - elif value == "and": - yield Token(TokenType.AND, value, pos) - elif value == "not": - yield Token(TokenType.NOT, value, pos) - else: - yield Token(TokenType.IDENT, value, pos) - pos += len(value) - else: - raise SyntaxError( - f'unexpected character "{input[pos]}"', - (FILE_NAME, 1, pos + 1, input), - ) - yield Token(TokenType.EOF, "", pos) - - @overload - def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ... - - @overload - def accept( - self, type: TokenType, *, reject: Literal[False] = False - ) -> Token | None: ... - - def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: - if self.current.type is type: - token = self.current - if token.type is not TokenType.EOF: - self.current = next(self.tokens) - return token - if reject: - self.reject((type,)) - return None - - def reject(self, expected: Sequence[TokenType]) -> NoReturn: - raise SyntaxError( - "expected {}; got {}".format( - " OR ".join(type.value for type in expected), - self.current.type.value, - ), - (FILE_NAME, 1, self.current.pos + 1, self.input), - ) - - -# True, False and None are legal match expression identifiers, -# but illegal as Python identifiers. To fix this, this prefix -# is added to identifiers in the conversion to Python AST. -IDENT_PREFIX = "$" - - -def expression(s: Scanner) -> ast.Expression: - if s.accept(TokenType.EOF): - ret: ast.expr = ast.Constant(False) - else: - ret = expr(s) - s.accept(TokenType.EOF, reject=True) - return ast.fix_missing_locations(ast.Expression(ret)) - - -def expr(s: Scanner) -> ast.expr: - ret = and_expr(s) - while s.accept(TokenType.OR): - rhs = and_expr(s) - ret = ast.BoolOp(ast.Or(), [ret, rhs]) - return ret - - -def and_expr(s: Scanner) -> ast.expr: - ret = not_expr(s) - while s.accept(TokenType.AND): - rhs = not_expr(s) - ret = ast.BoolOp(ast.And(), [ret, rhs]) - return ret - - -def not_expr(s: Scanner) -> ast.expr: - if s.accept(TokenType.NOT): - return ast.UnaryOp(ast.Not(), not_expr(s)) - if s.accept(TokenType.LPAREN): - ret = expr(s) - s.accept(TokenType.RPAREN, reject=True) - return ret - ident = s.accept(TokenType.IDENT) - if ident: - name = ast.Name(IDENT_PREFIX + ident.value, ast.Load()) - if s.accept(TokenType.LPAREN): - ret = ast.Call(func=name, args=[], keywords=all_kwargs(s)) - s.accept(TokenType.RPAREN, reject=True) - else: - ret = name - return ret - - s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT)) - - -BUILTIN_MATCHERS = {"True": True, "False": False, "None": None} - - -def single_kwarg(s: Scanner) -> ast.keyword: - keyword_name = s.accept(TokenType.IDENT, reject=True) - if not keyword_name.value.isidentifier(): - raise SyntaxError( - f"not a valid python identifier {keyword_name.value}", - (FILE_NAME, 1, keyword_name.pos + 1, s.input), - ) - if keyword.iskeyword(keyword_name.value): - raise SyntaxError( - f"unexpected reserved python keyword `{keyword_name.value}`", - (FILE_NAME, 1, keyword_name.pos + 1, s.input), - ) - s.accept(TokenType.EQUAL, reject=True) - - if value_token := s.accept(TokenType.STRING): - value: str | int | bool | None = value_token.value[1:-1] # strip quotes - else: - value_token = s.accept(TokenType.IDENT, reject=True) - if (number := value_token.value).isdigit() or ( - number.startswith("-") and number[1:].isdigit() - ): - value = int(number) - elif value_token.value in BUILTIN_MATCHERS: - value = BUILTIN_MATCHERS[value_token.value] - else: - raise SyntaxError( - f'unexpected character/s "{value_token.value}"', - (FILE_NAME, 1, value_token.pos + 1, s.input), - ) - - ret = ast.keyword(keyword_name.value, ast.Constant(value)) - return ret - - -def all_kwargs(s: Scanner) -> list[ast.keyword]: - ret = [single_kwarg(s)] - while s.accept(TokenType.COMMA): - ret.append(single_kwarg(s)) - return ret - - -class ExpressionMatcher(Protocol): - """A callable which, given an identifier and optional kwargs, should return - whether it matches in an :class:`Expression` evaluation. - - Should be prepared to handle arbitrary strings as input. - - If no kwargs are provided, the expression of the form `foo`. - If kwargs are provided, the expression is of the form `foo(1, b=True, "s")`. - - If the expression is not supported (e.g. don't want to accept the kwargs - syntax variant), should raise :class:`~pytest.UsageError`. - - Example:: - - def matcher(name: str, /, **kwargs: str | int | bool | None) -> bool: - # Match `cat`. - if name == "cat" and not kwargs: - return True - # Match `dog(barks=True)`. - if name == "dog" and kwargs == {"barks": False}: - return True - return False - """ - - def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: ... - - -@dataclasses.dataclass -class MatcherNameAdapter: - matcher: ExpressionMatcher - name: str - - def __bool__(self) -> bool: - return self.matcher(self.name) - - def __call__(self, **kwargs: str | int | bool | None) -> bool: - return self.matcher(self.name, **kwargs) - - -class MatcherAdapter(Mapping[str, MatcherNameAdapter]): - """Adapts a matcher function to a locals mapping as required by eval().""" - - def __init__(self, matcher: ExpressionMatcher) -> None: - self.matcher = matcher - - def __getitem__(self, key: str) -> MatcherNameAdapter: - return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :]) - - def __iter__(self) -> Iterator[str]: - raise NotImplementedError() - - def __len__(self) -> int: - raise NotImplementedError() - - -@final -class Expression: - """A compiled match expression as used by -k and -m. - - The expression can be evaluated against different matchers. - """ - - __slots__ = ("_code", "input") - - def __init__(self, input: str, code: types.CodeType) -> None: - #: The original input line, as a string. - self.input: Final = input - self._code: Final = code - - @classmethod - def compile(cls, input: str) -> Expression: - """Compile a match expression. - - :param input: The input expression - one line. - - :raises SyntaxError: If the expression is malformed. - """ - astexpr = expression(Scanner(input)) - code = compile( - astexpr, - filename="", - mode="eval", - ) - return Expression(input, code) - - def evaluate(self, matcher: ExpressionMatcher) -> bool: - """Evaluate the match expression. - - :param matcher: - A callback which determines whether an identifier matches or not. - See the :class:`ExpressionMatcher` protocol for details and example. - - :returns: Whether the expression matches or not. - - :raises UsageError: - If the matcher doesn't support the expression. Cannot happen if the - matcher supports all expressions. - """ - return bool(eval(self._code, {"__builtins__": {}}, MatcherAdapter(matcher))) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/structures.py b/tests/venv2/lib/python3.11/site-packages/_pytest/mark/structures.py deleted file mode 100644 index 5449b17..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/mark/structures.py +++ /dev/null @@ -1,695 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import collections.abc -from collections.abc import Callable -from collections.abc import Collection -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import MutableMapping -from collections.abc import Sequence -import dataclasses -import enum -import inspect -from typing import Any -from typing import final -from typing import NamedTuple -from typing import overload -from typing import TYPE_CHECKING -from typing import TypeVar -import warnings - -from .._code import getfslineno -from ..compat import NOTSET -from ..compat import NotSetType -from _pytest.config import Config -from _pytest.deprecated import check_ispytest -from _pytest.deprecated import PARAMETRIZE_NON_COLLECTION_ITERABLE -from _pytest.outcomes import fail -from _pytest.raises import AbstractRaises -from _pytest.scope import ScopeName -from _pytest.warning_types import PytestCollectionWarning -from _pytest.warning_types import PytestUnknownMarkWarning - - -if TYPE_CHECKING: - from ..nodes import Node - - -EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" - - -# Singleton type for HIDDEN_PARAM, as described in: -# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions -class _HiddenParam(enum.Enum): - token = 0 - - -#: Can be used as a parameter set id to hide it from the test name. -HIDDEN_PARAM = _HiddenParam.token - - -def istestfunc(func) -> bool: - return callable(func) and getattr(func, "__name__", "") != "" - - -def get_empty_parameterset_mark( - config: Config, argnames: Sequence[str], func -) -> MarkDecorator: - from ..nodes import Collector - - argslisting = ", ".join(argnames) - - _fs, lineno = getfslineno(func) - reason = f"got empty parameter set for ({argslisting})" - requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) - if requested_mark in ("", None, "skip"): - mark = MARK_GEN.skip(reason=reason) - elif requested_mark == "xfail": - mark = MARK_GEN.xfail(reason=reason, run=False) - elif requested_mark == "fail_at_collect": - raise Collector.CollectError( - f"Empty parameter set in '{func.__name__}' at line {lineno + 1}" - ) - else: - raise LookupError(requested_mark) - return mark - - -class ParameterSet(NamedTuple): - """A set of values for a set of parameters along with associated marks and - an optional ID for the set. - - Examples:: - - pytest.param(1, 2, 3) - # ParameterSet(values=(1, 2, 3), marks=(), id=None) - - pytest.param("hello", id="greeting") - # ParameterSet(values=("hello",), marks=(), id="greeting") - - # Parameter set with marks - pytest.param(42, marks=pytest.mark.xfail) - # ParameterSet(values=(42,), marks=(MarkDecorator(...),), id=None) - - # From parametrize mark (parameter names + list of parameter sets) - pytest.mark.parametrize( - ("a", "b", "expected"), - [ - (1, 2, 3), - pytest.param(40, 2, 42, id="everything"), - ], - ) - # ParameterSet(values=(1, 2, 3), marks=(), id=None) - # ParameterSet(values=(40, 2, 42), marks=(), id="everything") - """ - - values: Sequence[object | NotSetType] - marks: Collection[MarkDecorator | Mark] - id: str | _HiddenParam | None - - @classmethod - def param( - cls, - *values: object, - marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), - id: str | _HiddenParam | None = None, - ) -> ParameterSet: - if isinstance(marks, MarkDecorator): - marks = (marks,) - else: - assert isinstance(marks, collections.abc.Collection) - if any(i.name == "usefixtures" for i in marks): - raise ValueError( - "pytest.param cannot add pytest.mark.usefixtures; see " - "https://docs.pytest.org/en/stable/reference/reference.html#pytest-param" - ) - - if id is not None: - if not isinstance(id, str) and id is not HIDDEN_PARAM: - raise TypeError( - "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, " - f"got {type(id)}: {id!r}", - ) - return cls(values, marks, id) - - @classmethod - def extract_from( - cls, - parameterset: ParameterSet | Sequence[object] | object, - force_tuple: bool = False, - ) -> ParameterSet: - """Extract from an object or objects. - - :param parameterset: - A legacy style parameterset that may or may not be a tuple, - and may or may not be wrapped into a mess of mark objects. - - :param force_tuple: - Enforce tuple wrapping so single argument tuple values - don't get decomposed and break tests. - """ - if isinstance(parameterset, cls): - return parameterset - if force_tuple: - return cls.param(parameterset) - else: - # TODO: Refactor to fix this type-ignore. Currently the following - # passes type-checking but crashes: - # - # @pytest.mark.parametrize(('x', 'y'), [1, 2]) - # def test_foo(x, y): pass - return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] - - @staticmethod - def _parse_parametrize_args( - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], - *args, - **kwargs, - ) -> tuple[Sequence[str], bool]: - if isinstance(argnames, str): - # A trailing comma indicates tuple-style: "arg," is equivalent to ("arg",) - # In this case, argvalues should be a list of tuples, not wrapped values. - # See https://github.com/pytest-dev/pytest/issues/719 - has_trailing_comma = argnames.rstrip().endswith(",") - argnames = [x.strip() for x in argnames.split(",") if x.strip()] - force_tuple = len(argnames) == 1 and not has_trailing_comma - else: - force_tuple = False - return argnames, force_tuple - - @staticmethod - def _parse_parametrize_parameters( - argvalues: Iterable[ParameterSet | Sequence[object] | object], - force_tuple: bool, - ) -> list[ParameterSet]: - return [ - ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues - ] - - @classmethod - def _for_parametrize( - cls, - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], - func, - config: Config, - nodeid: str, - ) -> tuple[Sequence[str], list[ParameterSet]]: - if not isinstance(argvalues, Collection): - warnings.warn( - PARAMETRIZE_NON_COLLECTION_ITERABLE.format( - nodeid=nodeid, - type_name=type(argvalues).__name__, - ), - stacklevel=3, - ) - - argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) - parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) - del argvalues - - if parameters: - # Check all parameter sets have the correct number of values. - for param in parameters: - if len(param.values) != len(argnames): - msg = ( - '{nodeid}: in "parametrize" the number of names ({names_len}):\n' - " {names}\n" - "must be equal to the number of values ({values_len}):\n" - " {values}" - ) - fail( - msg.format( - nodeid=nodeid, - values=param.values, - names=argnames, - names_len=len(argnames), - values_len=len(param.values), - ), - pytrace=False, - ) - else: - # Empty parameter set (likely computed at runtime): create a single - # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. - mark = get_empty_parameterset_mark(config, argnames, func) - parameters.append( - ParameterSet( - values=(NOTSET,) * len(argnames), marks=[mark], id="NOTSET" - ) - ) - return argnames, parameters - - -@final -@dataclasses.dataclass(frozen=True) -class Mark: - """A pytest mark.""" - - #: Name of the mark. - name: str - #: Positional arguments of the mark decorator. - args: tuple[Any, ...] - #: Keyword arguments of the mark decorator. - kwargs: Mapping[str, Any] - - #: Source Mark for ids with parametrize Marks. - _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) - #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Sequence[str] | None = dataclasses.field( - default=None, repr=False - ) - - def __init__( - self, - name: str, - args: tuple[Any, ...], - kwargs: Mapping[str, Any], - param_ids_from: Mark | None = None, - param_ids_generated: Sequence[str] | None = None, - *, - _ispytest: bool = False, - ) -> None: - """:meta private:""" - check_ispytest(_ispytest) - # Weirdness to bypass frozen=True. - object.__setattr__(self, "name", name) - object.__setattr__(self, "args", args) - object.__setattr__(self, "kwargs", kwargs) - object.__setattr__(self, "_param_ids_from", param_ids_from) - object.__setattr__(self, "_param_ids_generated", param_ids_generated) - - def _has_param_ids(self) -> bool: - return "ids" in self.kwargs or len(self.args) >= 4 - - def combined_with(self, other: Mark) -> Mark: - """Return a new Mark which is a combination of this - Mark and another Mark. - - Combines by appending args and merging kwargs. - - :param Mark other: The mark to combine with. - :rtype: Mark - """ - assert self.name == other.name - - # Remember source of ids with parametrize Marks. - param_ids_from: Mark | None = None - if self.name == "parametrize": - if other._has_param_ids(): - param_ids_from = other - elif self._has_param_ids(): - param_ids_from = self - - return Mark( - self.name, - self.args + other.args, - dict(self.kwargs, **other.kwargs), - param_ids_from=param_ids_from, - _ispytest=True, - ) - - -# A generic parameter designating an object to which a Mark may -# be applied -- a test function (callable) or class. -# Note: a lambda is not allowed, but this can't be represented. -Markable = TypeVar("Markable", bound=Callable[..., object] | type) - - -@dataclasses.dataclass -class MarkDecorator: - """A decorator for applying a mark on test functions and classes. - - ``MarkDecorators`` are created with ``pytest.mark``:: - - mark1 = pytest.mark.NAME # Simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator - - and can then be applied as decorators to test functions:: - - @mark2 - def test_function(): - pass - - When a ``MarkDecorator`` is called, it does the following: - - 1. If called with a single class as its only positional argument and no - additional keyword arguments, it attaches the mark to the class so it - gets applied automatically to all test cases found in that class. - - 2. If called with a single function as its only positional argument and - no additional keyword arguments, it attaches the mark to the function, - containing all the arguments already stored internally in the - ``MarkDecorator``. - - 3. When called in any other case, it returns a new ``MarkDecorator`` - instance with the original ``MarkDecorator``'s content updated with - the arguments passed to this call. - - Note: The rules above prevent a ``MarkDecorator`` from storing only a - single function or class reference as its positional argument with no - additional keyword or positional arguments. You can work around this by - using `with_args()`. - """ - - mark: Mark - - def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: - """:meta private:""" - check_ispytest(_ispytest) - self.mark = mark - - @property - def name(self) -> str: - """Alias for mark.name.""" - return self.mark.name - - @property - def args(self) -> tuple[Any, ...]: - """Alias for mark.args.""" - return self.mark.args - - @property - def kwargs(self) -> Mapping[str, Any]: - """Alias for mark.kwargs.""" - return self.mark.kwargs - - @property - def markname(self) -> str: - """:meta private:""" - return self.name # for backward-compat (2.4.1 had this attr) - - def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: - """Return a MarkDecorator with extra arguments added. - - Unlike calling the MarkDecorator, with_args() can be used even - if the sole argument is a callable/class. - """ - mark = Mark(self.name, args, kwargs, _ispytest=True) - return MarkDecorator(self.mark.combined_with(mark), _ispytest=True) - - # Type ignored because the overloads overlap with an incompatible - # return type. Not much we can do about that. Thankfully mypy picks - # the first match so it works out even if we break the rules. - @overload - def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] - pass - - @overload - def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: - pass - - def __call__(self, *args: object, **kwargs: object): - """Call the MarkDecorator.""" - if args and not kwargs: - func = args[0] - is_class = inspect.isclass(func) - # For staticmethods/classmethods, the marks are eventually fetched from the - # function object, not the descriptor, so unwrap. - unwrapped_func = func - if isinstance(func, staticmethod | classmethod): - unwrapped_func = func.__func__ - if len(args) == 1 and (istestfunc(unwrapped_func) or is_class): - store_mark(unwrapped_func, self.mark) - return func - return self.with_args(*args, **kwargs) - - -def get_unpacked_marks( - obj: object | type, - *, - consider_mro: bool = True, -) -> list[Mark]: - """Obtain the unpacked marks that are stored on an object. - - If obj is a class and consider_mro is true, return marks applied to - this class and all of its super-classes in MRO order. If consider_mro - is false, only return marks applied directly to this class. - """ - if isinstance(obj, type): - if not consider_mro: - mark_lists = [obj.__dict__.get("pytestmark", [])] - else: - mark_lists = [ - x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__) - ] - mark_list = [] - for item in mark_lists: - if isinstance(item, list): - mark_list.extend(item) - else: - mark_list.append(item) - else: - mark_attribute = getattr(obj, "pytestmark", []) - if mark_attribute is None: - warnings.warn( - "Module defines a `__getattr__` which returns None for " - "'pytestmark' instead of raising AttributeError. " - "Make sure `__getattr__` raises AttributeError for " - "attributes it does not provide. " - "See https://github.com/pytest-dev/pytest/issues/8265", - PytestCollectionWarning, - stacklevel=2, - ) - mark_list = [] - elif isinstance(mark_attribute, list): - mark_list = mark_attribute - else: - mark_list = [mark_attribute] - return list(normalize_mark_list(mark_list)) - - -def normalize_mark_list( - mark_list: Iterable[Mark | MarkDecorator], -) -> Iterable[Mark]: - """ - Normalize an iterable of Mark or MarkDecorator objects into a list of marks - by retrieving the `mark` attribute on MarkDecorator instances. - - :param mark_list: marks to normalize - :returns: A new list of the extracted Mark objects - """ - for mark in mark_list: - mark_obj = getattr(mark, "mark", mark) - if not isinstance(mark_obj, Mark): - raise TypeError(f"got {mark_obj!r} instead of Mark") - yield mark_obj - - -def store_mark(obj, mark: Mark) -> None: - """Store a Mark on an object. - - This is used to implement the Mark declarations/decorators correctly. - """ - assert isinstance(mark, Mark), mark - - from ..fixtures import getfixturemarker - - if getfixturemarker(obj) is not None: - fail( - "Marks cannot be applied to fixtures.\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function" - ) - - # Always reassign name to avoid updating pytestmark in a reference that - # was only borrowed. - obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] - - -# Typing for builtin pytest marks. This is cheating; it gives builtin marks -# special privilege, and breaks modularity. But practicality beats purity... -if TYPE_CHECKING: - - class _SkipMarkDecorator(MarkDecorator): - @overload # type: ignore[override,no-overload-impl] - def __call__(self, arg: Markable) -> Markable: ... - - @overload - def __call__(self, reason: str = ...) -> MarkDecorator: ... - - class _SkipifMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, - condition: str | bool = ..., - *conditions: str | bool, - reason: str = ..., - ) -> MarkDecorator: ... - - class _XfailMarkDecorator(MarkDecorator): - @overload # type: ignore[override,no-overload-impl] - def __call__(self, arg: Markable) -> Markable: ... - - @overload - def __call__( - self, - condition: str | bool = True, - *conditions: str | bool, - reason: str = ..., - run: bool = ..., - raises: None - | type[BaseException] - | tuple[type[BaseException], ...] - | AbstractRaises[BaseException] = ..., - strict: bool = ..., - ) -> MarkDecorator: ... - - class _ParametrizeMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], - # TODO(pytest10): Change to below after PARAMETRIZE_NON_COLLECTION_ITERABLE deprecation. - # Overload doesn't work, see #14606. - # argvalues: Collection[ParameterSet | Sequence[object] | object], - *, - indirect: bool | Sequence[str] = ..., - ids: Iterable[None | str | float | int | bool | _HiddenParam] - | Callable[[Any], object | None] - | None = ..., - scope: ScopeName | None = ..., - ) -> MarkDecorator: ... - - class _UsefixturesMarkDecorator(MarkDecorator): - def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override] - ... - - class _FilterwarningsMarkDecorator(MarkDecorator): - def __call__(self, *filters: str) -> MarkDecorator: # type: ignore[override] - ... - - -@final -class MarkGenerator: - """Factory for :class:`MarkDecorator` objects - exposed as - a ``pytest.mark`` singleton instance. - - Example:: - - import pytest - - - @pytest.mark.slowtest - def test_function(): - pass - - applies a 'slowtest' :class:`Mark` on ``test_function``. - """ - - # See TYPE_CHECKING above. - if TYPE_CHECKING: - skip: _SkipMarkDecorator - skipif: _SkipifMarkDecorator - xfail: _XfailMarkDecorator - parametrize: _ParametrizeMarkDecorator - usefixtures: _UsefixturesMarkDecorator - filterwarnings: _FilterwarningsMarkDecorator - - def __init__(self, *, _ispytest: bool = False) -> None: - check_ispytest(_ispytest) - self._config: Config | None = None - self._markers: set[str] = set() - - def __getattr__(self, name: str) -> MarkDecorator: - """Generate a new :class:`MarkDecorator` with the given name.""" - if name[0] == "_": - raise AttributeError("Marker name must NOT start with underscore") - - if self._config is not None: - # We store a set of markers as a performance optimisation - if a mark - # name is in the set we definitely know it, but a mark may be known and - # not in the set. We therefore start by updating the set! - if name not in self._markers: - for line in self._config.getini("markers"): - # example lines: "skipif(condition): skip the given test if..." - # or "hypothesis: tests which use Hypothesis", so to get the - # marker name we split on both `:` and `(`. - marker = line.split(":")[0].split("(")[0].strip() - self._markers.add(marker) - - # If the name is not in the set of known marks after updating, - # then it really is time to issue a warning or an error. - if name not in self._markers: - # Raise a specific error for common misspellings of "parametrize". - if name in ["parameterize", "parametrise", "parameterise"]: - __tracebackhide__ = True - fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") - - strict_markers = self._config.getini("strict_markers") - if strict_markers is None: - strict_markers = self._config.getini("strict") - if strict_markers: - fail( - f"{name!r} not found in `markers` configuration option", - pytrace=False, - ) - - warnings.warn( - f"Unknown pytest.mark.{name} - is this a typo? You can register " - "custom marks to avoid this warning - for details, see " - "https://docs.pytest.org/en/stable/how-to/mark.html", - PytestUnknownMarkWarning, - 2, - ) - - return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True) - - -MARK_GEN = MarkGenerator(_ispytest=True) - - -@final -class NodeKeywords(MutableMapping[str, Any]): - __slots__ = ("_markers", "node", "parent") - - def __init__(self, node: Node) -> None: - self.node = node - self.parent = node.parent - self._markers = {node.name: True} - - def __getitem__(self, key: str) -> Any: - try: - return self._markers[key] - except KeyError: - if self.parent is None: - raise - return self.parent.keywords[key] - - def __setitem__(self, key: str, value: Any) -> None: - self._markers[key] = value - - # Note: we could've avoided explicitly implementing some of the methods - # below and use the collections.abc fallback, but that would be slow. - - def __contains__(self, key: object) -> bool: - return key in self._markers or ( - self.parent is not None and key in self.parent.keywords - ) - - def update( # type: ignore[override] - self, - other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), - **kwds: Any, - ) -> None: - self._markers.update(other) - self._markers.update(kwds) - - def __delitem__(self, key: str) -> None: - raise ValueError("cannot delete key in keywords dict") - - def __iter__(self) -> Iterator[str]: - # Doesn't need to be fast. - yield from self._markers - if self.parent is not None: - for keyword in self.parent.keywords: - # self._marks and self.parent.keywords can have duplicates. - if keyword not in self._markers: - yield keyword - - def __len__(self) -> int: - # Doesn't need to be fast. - return sum(1 for keyword in self) - - def __repr__(self) -> str: - return f"" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/monkeypatch.py b/tests/venv2/lib/python3.11/site-packages/_pytest/monkeypatch.py deleted file mode 100644 index d6db724..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/monkeypatch.py +++ /dev/null @@ -1,430 +0,0 @@ -# mypy: allow-untyped-defs -"""Monkeypatching and mocking functionality.""" - -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Mapping -from collections.abc import MutableMapping -from contextlib import contextmanager -import importlib -import os -from pathlib import Path -import re -import sys -from typing import Any -from typing import final -from typing import overload -from typing import TypeVar -import warnings - -from _pytest.compat import NOTSET -from _pytest.compat import NotSetType -from _pytest.deprecated import MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES -from _pytest.fixtures import fixture -from _pytest.warning_types import PytestWarning - - -RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") - - -K = TypeVar("K") -V = TypeVar("V") - - -@fixture -def monkeypatch() -> Generator[MonkeyPatch]: - """A convenient fixture for monkey-patching. - - The fixture provides these methods to modify objects, dictionaries, or - :data:`os.environ`: - - * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` - * :meth:`monkeypatch.delattr(obj, name, raising=True) ` - * :meth:`monkeypatch.setitem(mapping, name, value) ` - * :meth:`monkeypatch.delitem(obj, name, raising=True) ` - * :meth:`monkeypatch.setenv(name, value, prepend=None) ` - * :meth:`monkeypatch.delenv(name, raising=True) ` - * :meth:`monkeypatch.syspath_prepend(path) ` - * :meth:`monkeypatch.chdir(path) ` - * :meth:`monkeypatch.context() ` - - All modifications will be undone after the requesting test function or - fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` - or :class:`AttributeError` will be raised if the set/deletion operation does not have the - specified target. - - To undo modifications done by the fixture in a contained scope, - use :meth:`context() `. - """ - mpatch = MonkeyPatch() - yield mpatch - mpatch.undo() - - -def resolve(name: str) -> object: - # Simplified from zope.dottedname. - parts = name.split(".") - - used = parts.pop(0) - found: object = importlib.import_module(used) - for part in parts: - used += "." + part - try: - found = getattr(found, part) - except AttributeError: - pass - else: - continue - # We use explicit un-nesting of the handling block in order - # to avoid nested exceptions. - try: - importlib.import_module(used) - except ImportError as ex: - expected = str(ex).split()[-1] - if expected == used: - raise - else: - raise ImportError(f"import error in {used}: {ex}") from ex - found = annotated_getattr(found, part, used) - return found - - -def annotated_getattr(obj: object, name: str, ann: str) -> object: - try: - obj = getattr(obj, name) - except AttributeError as e: - raise AttributeError( - f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}" - ) from e - return obj - - -def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: - if not isinstance(import_path, str) or "." not in import_path: - raise TypeError(f"must be absolute import path string, not {import_path!r}") - module, attr = import_path.rsplit(".", 1) - target = resolve(module) - if raising: - annotated_getattr(target, attr, ann=module) - return attr, target - - -@final -class MonkeyPatch: - """Helper to conveniently monkeypatch attributes/items/environment - variables/syspath. - - Returned by the :fixture:`monkeypatch` fixture. - - .. versionchanged:: 6.2 - Can now also be used directly as `pytest.MonkeyPatch()`, for when - the fixture is not available. In this case, use - :meth:`with MonkeyPatch.context() as mp: ` or remember to call - :meth:`undo` explicitly. - """ - - def __init__(self) -> None: - self._setattr: list[tuple[object, str, object]] = [] - self._setitem: list[tuple[Mapping[Any, Any], object, object]] = [] - self._cwd: str | None = None - self._savesyspath: list[str] | None = None - - @classmethod - @contextmanager - def context(cls) -> Generator[MonkeyPatch]: - """Context manager that returns a new :class:`MonkeyPatch` object - which undoes any patching done inside the ``with`` block upon exit. - - Example: - - .. code-block:: python - - import functools - - - def test_partial(monkeypatch): - with monkeypatch.context() as m: - m.setattr(functools, "partial", 3) - - Useful in situations where it is desired to undo some patches before the test ends, - such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples - of this see :issue:`3290`). - """ - m = cls() - try: - yield m - finally: - m.undo() - - @overload - def setattr( - self, - target: str, - name: object, - value: NotSetType = ..., - raising: bool = ..., - ) -> None: ... - - @overload - def setattr( - self, - target: object, - name: str, - value: object, - raising: bool = ..., - ) -> None: ... - - def setattr( - self, - target: str | object, - name: object | str, - value: object = NOTSET, - raising: bool = True, - ) -> None: - """ - Set attribute value on target, memorizing the old value. - - For example: - - .. code-block:: python - - import os - - monkeypatch.setattr(os, "getcwd", lambda: "/") - - The code above replaces the :func:`os.getcwd` function by a ``lambda`` which - always returns ``"/"``. - - For convenience, you can specify a string as ``target`` which - will be interpreted as a dotted import path, with the last part - being the attribute name: - - .. code-block:: python - - monkeypatch.setattr("os.getcwd", lambda: "/") - - Raises :class:`AttributeError` if the attribute does not exist, unless - ``raising`` is set to False. - - **Where to patch** - - ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one. - There can be many names pointing to any individual object, so for patching to work you must ensure - that you patch the name used by the system under test. - - See the section :ref:`Where to patch ` in the :mod:`unittest.mock` - docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but - applies to ``monkeypatch.setattr`` as well. - """ - __tracebackhide__ = True - import inspect - - if value is NOTSET: - if not isinstance(target, str): - raise TypeError( - "use setattr(target, name, value) or " - "setattr(target, value) with target being a dotted " - "import string" - ) - value = name - name, target = derive_importpath(target, raising) - else: - if not isinstance(name, str): - raise TypeError( - "use setattr(target, name, value) with name being a string or " - "setattr(target, value) with target being a dotted " - "import string" - ) - - oldval = getattr(target, name, NOTSET) - if raising and oldval is NOTSET: - raise AttributeError(f"{target!r} has no attribute {name!r}") - - # avoid class descriptors like staticmethod/classmethod - if inspect.isclass(target): - oldval = target.__dict__.get(name, NOTSET) - setattr(target, name, value) - self._setattr.append((target, name, oldval)) - - def delattr( - self, - target: object | str, - name: str | NotSetType = NOTSET, - raising: bool = True, - ) -> None: - """Delete attribute ``name`` from ``target``. - - If no ``name`` is specified and ``target`` is a string - it will be interpreted as a dotted import path with the - last part being the attribute name. - - Raises AttributeError it the attribute does not exist, unless - ``raising`` is set to False. - """ - __tracebackhide__ = True - import inspect - - if name is NOTSET: - if not isinstance(target, str): - raise TypeError( - "use delattr(target, name) or " - "delattr(target) with target being a dotted " - "import string" - ) - name, target = derive_importpath(target, raising) - - if not hasattr(target, name): - if raising: - raise AttributeError(name) - else: - oldval = getattr(target, name, NOTSET) - # Avoid class descriptors like staticmethod/classmethod. - if inspect.isclass(target): - oldval = target.__dict__.get(name, NOTSET) - self._setattr.append((target, name, oldval)) - delattr(target, name) - - def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None: - """Set dictionary entry ``name`` to value.""" - self._setitem.append((dic, name, dic.get(name, NOTSET))) - # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict - dic[name] = value # type: ignore[index] - - def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: - """Delete ``name`` from dict. - - Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to - False. - """ - if name not in dic: - if raising: - raise KeyError(name) - else: - self._setitem.append((dic, name, dic.get(name, NOTSET))) - # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict - del dic[name] # type: ignore[attr-defined] - - def setenv(self, name: str, value: str, prepend: str | None = None) -> None: - """Set environment variable ``name`` to ``value``. - - If ``prepend`` is a character, read the current environment variable - value and prepend the ``value`` adjoined with the ``prepend`` - character. - """ - if not isinstance(value, str): - warnings.warn( # type: ignore[unreachable] - PytestWarning( - f"Value of environment variable {name} type should be str, but got " - f"{value!r} (type: {type(value).__name__}); converted to str implicitly" - ), - stacklevel=2, - ) - value = str(value) - if prepend and name in os.environ: - value = value + prepend + os.environ[name] - self.setitem(os.environ, name, value) - - def delenv(self, name: str, raising: bool = True) -> None: - """Delete ``name`` from the environment. - - Raises ``KeyError`` if it does not exist, unless ``raising`` is set to - False. - """ - environ: MutableMapping[str, str] = os.environ - self.delitem(environ, name, raising=raising) - - def syspath_prepend(self, path) -> None: - """Prepend ``path`` to ``sys.path`` list of import locations.""" - if self._savesyspath is None: - self._savesyspath = sys.path[:] - sys.path.insert(0, str(path)) - - # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171 - # this is only needed when pkg_resources was already loaded by the namespace package - if "pkg_resources" in sys.modules: - import pkg_resources - from pkg_resources import fixup_namespace_packages - - # Only issue deprecation warning if this call would actually have an - # effect for this specific path. - if ( - hasattr(pkg_resources, "_namespace_packages") - and pkg_resources._namespace_packages - ): - path_obj = Path(str(path)) - for ns_pkg in pkg_resources._namespace_packages: - if ns_pkg is None: - continue - ns_pkg_path = path_obj / ns_pkg.replace(".", os.sep) - if ns_pkg_path.is_dir(): - warnings.warn( - MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES, stacklevel=2 - ) - break - - fixup_namespace_packages(str(path)) - - # A call to syspathinsert() usually means that the caller wants to - # import some dynamically created files, thus with python3 we - # invalidate its import caches. - # This is especially important when any namespace package is in use, - # since then the mtime based FileFinder cache (that gets created in - # this case already) gets not invalidated when writing the new files - # quickly afterwards. - from importlib import invalidate_caches - - invalidate_caches() - - def chdir(self, path: str | os.PathLike[str]) -> None: - """Change the current working directory to the specified path. - - :param path: - The path to change into. - """ - if self._cwd is None: - self._cwd = os.getcwd() - os.chdir(path) - - def undo(self) -> None: - """Undo previous changes. - - This call consumes the undo stack. Calling it a second time has no - effect unless you do more monkeypatching after the undo call. - - There is generally no need to call `undo()`, since it is - called automatically during tear-down. - - .. note:: - The same `monkeypatch` fixture is used across a - single test function invocation. If `monkeypatch` is used both by - the test function itself and one of the test fixtures, - calling `undo()` will undo all of the changes made in - both functions. - - Prefer to use :meth:`context() ` instead. - """ - for obj, name, value in reversed(self._setattr): - if value is not NOTSET: - setattr(obj, name, value) - else: - delattr(obj, name) - self._setattr[:] = [] - for dictionary, key, value in reversed(self._setitem): - if value is NOTSET: - try: - # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict - del dictionary[key] # type: ignore[attr-defined] - except KeyError: - pass # Was already deleted, so we have the desired state. - else: - # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict - dictionary[key] = value # type: ignore[index] - self._setitem[:] = [] - if self._savesyspath is not None: - sys.path[:] = self._savesyspath - self._savesyspath = None - - if self._cwd is not None: - os.chdir(self._cwd) - self._cwd = None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/nodes.py b/tests/venv2/lib/python3.11/site-packages/_pytest/nodes.py deleted file mode 100644 index f0629c2..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/nodes.py +++ /dev/null @@ -1,764 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import abc -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import MutableMapping -from functools import cached_property -from functools import lru_cache -import os -import pathlib -from pathlib import Path -from typing import Any -from typing import cast -from typing import NoReturn -from typing import overload -from typing import TYPE_CHECKING -from typing import TypeVar -import warnings - -import pluggy - -import _pytest._code -from _pytest._code import getfslineno -from _pytest._code.code import ExceptionInfo -from _pytest._code.code import TerminalRepr -from _pytest._code.code import Traceback -from _pytest._code.code import TracebackStyle -from _pytest.compat import LEGACY_PATH -from _pytest.compat import signature -from _pytest.config import Config -from _pytest.config import ConftestImportFailure -from _pytest.mark.structures import Mark -from _pytest.mark.structures import MarkDecorator -from _pytest.mark.structures import NodeKeywords -from _pytest.outcomes import fail -from _pytest.pathlib import absolutepath -from _pytest.stash import Stash -from _pytest.warning_types import PytestWarning - - -if TYPE_CHECKING: - from typing_extensions import Self - - # Imported here due to circular import. - from _pytest.main import Session - - -SEP = "/" - - -def norm_sep(path: str | os.PathLike[str]) -> str: - """Normalize path separators to forward slashes for nodeid compatibility. - - Replaces backslashes with forward slashes. This handles both Windows native - paths and cross-platform data (e.g., Windows paths in serialized test reports - when running on Linux). - - :param path: A path string or PathLike object. - :returns: String with all backslashes replaced by forward slashes. - """ - return os.fspath(path).replace("\\", SEP) - - -tracebackcutdir = Path(_pytest.__file__).parent - - -_T = TypeVar("_T") - - -_NodeType = TypeVar("_NodeType", bound="Node") - - -class NodeMeta(abc.ABCMeta): - """Metaclass used by :class:`Node` to enforce that direct construction raises - :class:`Failed`. - - This behaviour supports the indirection introduced with :meth:`Node.from_parent`, - the named constructor to be used instead of direct construction. The design - decision to enforce indirection with :class:`NodeMeta` was made as a - temporary aid for refactoring the collection tree, which was diagnosed to - have :class:`Node` objects whose creational patterns were overly entangled. - Once the refactoring is complete, this metaclass can be removed. - - See https://github.com/pytest-dev/pytest/projects/3 for an overview of the - progress on detangling the :class:`Node` classes. - """ - - def __call__(cls, *k, **kw) -> NoReturn: - msg = ( - "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" - "See " - "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" - " for more details." - ).format(name=f"{cls.__module__}.{cls.__name__}") - fail(msg, pytrace=False) - - def _create(cls: type[_T], *k, **kw) -> _T: - try: - return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] - except TypeError: - sig = signature(getattr(cls, "__init__")) - known_kw = {k: v for k, v in kw.items() if k in sig.parameters} - from .warning_types import PytestDeprecationWarning - - warnings.warn( - PytestDeprecationWarning( - f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n" - "See https://docs.pytest.org/en/stable/deprecations.html" - "#constructors-of-custom-pytest-node-subclasses-should-take-kwargs " - "for more details." - ) - ) - - return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc] - - -class Node(abc.ABC, metaclass=NodeMeta): - r"""Base class of :class:`Collector` and :class:`Item`, the components of - the test collection tree. - - ``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the - leaf nodes. - """ - - # Implemented in the legacypath plugin. - #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage - #: for methods not migrated to ``pathlib.Path`` yet, such as - #: :meth:`Item.reportinfo `. Will be deprecated in - #: a future release, prefer using :attr:`path` instead. - fspath: LEGACY_PATH - - # Use __slots__ to make attribute access faster. - # Note that __dict__ is still available. - __slots__ = ( - "__dict__", - "_nodeid", - "_store", - "config", - "name", - "parent", - "path", - "session", - ) - - def __init__( - self, - name: str, - parent: Node | None = None, - config: Config | None = None, - session: Session | None = None, - fspath: None = None, - path: Path | None = None, - nodeid: str | None = None, - ) -> None: - #: A unique name within the scope of the parent node. - self.name: str = name - - #: The parent collector node. - self.parent = parent - - if config: - #: The pytest config object. - self.config: Config = config - else: - if not parent: - raise TypeError("config or parent must be provided") - self.config = parent.config - - if session: - #: The pytest session this node is part of. - self.session: Session = session - else: - if not parent: - raise TypeError("session or parent must be provided") - self.session = parent.session - - if path is None: - assert parent is not None - path = parent.path - #: Filesystem path where this node was collected from. - self.path: pathlib.Path = path - - # The explicit annotation is to avoid publicly exposing NodeKeywords. - #: Keywords/markers collected from all scopes. - self.keywords: MutableMapping[str, Any] = NodeKeywords(self) - - #: The marker objects belonging to this node. - self.own_markers: list[Mark] = [] - - #: Allow adding of extra keywords to use for matching. - self.extra_keyword_matches: set[str] = set() - - if nodeid is not None: - assert "::()" not in nodeid - self._nodeid = nodeid - else: - if not self.parent: - raise TypeError("nodeid or parent must be provided") - self._nodeid = self.parent.nodeid + "::" + self.name - - #: A place where plugins can store information on the node for their - #: own use. - self.stash: Stash = Stash() - # Deprecated alias. Was never public. Can be removed in a few releases. - self._store = self.stash - - @classmethod - def from_parent(cls, parent: Node, **kw) -> Self: - """Public constructor for Nodes. - - This indirection got introduced in order to enable removing - the fragile logic from the node constructors. - - Subclasses can use ``super().from_parent(...)`` when overriding the - construction. - - :param parent: The parent node of this Node. - """ - if "config" in kw: - raise TypeError("config is not a valid argument for from_parent") - if "session" in kw: - raise TypeError("session is not a valid argument for from_parent") - return cls._create(parent=parent, **kw) - - @property - def ihook(self) -> pluggy.HookRelay: - """Path-sensitive hook proxy used to call pytest hooks.""" - return self.session.gethookproxy(self.path) - - def __repr__(self) -> str: - return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None)) - - def warn(self, warning: Warning) -> None: - """Issue a warning for this Node. - - Warnings will be displayed after the test session, unless explicitly suppressed. - - :param Warning warning: - The warning instance to issue. - - :raises ValueError: If ``warning`` instance is not a subclass of Warning. - - Example usage: - - .. code-block:: python - - node.warn(PytestWarning("some message")) - node.warn(UserWarning("some message")) - - .. versionchanged:: 6.2 - Any subclass of :class:`Warning` is now accepted, rather than only - :class:`PytestWarning ` subclasses. - """ - # enforce type checks here to avoid getting a generic type error later otherwise. - if not isinstance(warning, Warning): - raise ValueError( - f"warning must be an instance of Warning or subclass, got {warning!r}" - ) - path, lineno = get_fslocation_from_item(self) - assert lineno is not None - warnings.warn_explicit( - warning, - category=None, - filename=str(path), - lineno=lineno + 1, - ) - - # Methods for ordering nodes. - - @property - def nodeid(self) -> str: - """A ::-separated string denoting its collection tree address.""" - return self._nodeid - - def __hash__(self) -> int: - return hash(self._nodeid) - - def setup(self) -> None: - pass - - def teardown(self) -> None: - pass - - def iter_parents(self) -> Iterator[Node]: - """Iterate over all parent collectors starting from and including self - up to the root of the collection tree. - - .. versionadded:: 8.1 - """ - parent: Node | None = self - while parent is not None: - yield parent - parent = parent.parent - - def listchain(self) -> list[Node]: - """Return a list of all parent collectors starting from the root of the - collection tree down to and including self.""" - chain = [] - item: Node | None = self - while item is not None: - chain.append(item) - item = item.parent - chain.reverse() - return chain - - def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: - """Dynamically add a marker object to the node. - - :param marker: - The marker. - :param append: - Whether to append the marker, or prepend it. - """ - from _pytest.mark import MARK_GEN - - if isinstance(marker, MarkDecorator): - marker_ = marker - elif isinstance(marker, str): - marker_ = getattr(MARK_GEN, marker) - else: - raise ValueError("is not a string or pytest.mark.* Marker") - self.keywords[marker_.name] = marker_ - if append: - self.own_markers.append(marker_.mark) - else: - self.own_markers.insert(0, marker_.mark) - - def iter_markers(self, name: str | None = None) -> Iterator[Mark]: - """Iterate over all markers of the node. - - :param name: If given, filter the results by the name attribute. - :returns: An iterator of the markers of the node. - """ - return (x[1] for x in self.iter_markers_with_node(name=name)) - - def iter_markers_with_node( - self, name: str | None = None - ) -> Iterator[tuple[Node, Mark]]: - """Iterate over all markers of the node. - - :param name: If given, filter the results by the name attribute. - :returns: An iterator of (node, mark) tuples. - """ - for node in self.iter_parents(): - for mark in node.own_markers: - if name is None or getattr(mark, "name", None) == name: - yield node, mark - - @overload - def get_closest_marker(self, name: str) -> Mark | None: ... - - @overload - def get_closest_marker(self, name: str, default: Mark) -> Mark: ... - - def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None: - """Return the first marker matching the name, from closest (for - example function) to farther level (for example module level). - - :param default: Fallback return value if no marker was found. - :param name: Name to filter by. - """ - return next(self.iter_markers(name=name), default) - - def listextrakeywords(self) -> set[str]: - """Return a set of all extra keywords in self and any parents.""" - extra_keywords: set[str] = set() - for item in self.listchain(): - extra_keywords.update(item.extra_keyword_matches) - return extra_keywords - - def listnames(self) -> list[str]: - return [x.name for x in self.listchain()] - - def addfinalizer(self, fin: Callable[[], object]) -> None: - """Register a function to be called without arguments when this node is - finalized. - - This method can only be called when this node is active - in a setup chain, for example during self.setup(). - """ - self.session._setupstate.addfinalizer(fin, self) - - def getparent(self, cls: type[_NodeType]) -> _NodeType | None: - """Get the closest parent node (including self) which is an instance of - the given class. - - :param cls: The node class to search for. - :returns: The node, if found. - """ - for node in self.iter_parents(): - if isinstance(node, cls): - return node - return None - - def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: - return excinfo.traceback - - def _repr_failure_py( - self, - excinfo: ExceptionInfo[BaseException], - style: TracebackStyle | None = None, - ) -> TerminalRepr: - from _pytest.fixtures import FixtureLookupError - - if isinstance(excinfo.value, ConftestImportFailure): - excinfo = ExceptionInfo.from_exception(excinfo.value.cause) - if isinstance(excinfo.value, fail.Exception): - if not excinfo.value.pytrace: - style = "value" - if isinstance(excinfo.value, FixtureLookupError): - return excinfo.value.formatrepr() - - tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] - if self.config.getoption("fulltrace", False): - style = "long" - tbfilter = False - else: - tbfilter = self._traceback_filter - if style == "auto": - style = "long" - # XXX should excinfo.getrepr record all data and toterminal() process it? - if style is None: - if self.config.getoption("tbstyle", "auto") == "short": - style = "short" - else: - style = "long" - - if self.config.get_verbosity() > 1: - truncate_locals = False - else: - truncate_locals = True - - truncate_args = False if self.config.get_verbosity() > 2 else True - - # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False. - # It is possible for a fixture/test to change the CWD while this code runs, which - # would then result in the user seeing confusing paths in the failure message. - # To fix this, if the CWD changed, always display the full absolute path. - # It will be better to just always display paths relative to invocation_dir, but - # this requires a lot of plumbing (#6428). - try: - abspath = Path(os.getcwd()) != self.config.invocation_params.dir - except OSError: - abspath = True - - return excinfo.getrepr( - funcargs=True, - abspath=abspath, - showlocals=self.config.getoption("showlocals", False), - style=style, - tbfilter=tbfilter, - truncate_locals=truncate_locals, - truncate_args=truncate_args, - ) - - def repr_failure( - self, - excinfo: ExceptionInfo[BaseException], - style: TracebackStyle | None = None, - ) -> str | TerminalRepr: - """Return a representation of a collection or test failure. - - .. seealso:: :ref:`non-python tests` - - :param excinfo: Exception information for the failure. - """ - return self._repr_failure_py(excinfo, style) - - -def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]: - """Try to extract the actual location from a node, depending on available attributes: - - * "location": a pair (path, lineno) - * "obj": a Python object that the node wraps. - * "path": just a path - - :rtype: A tuple of (str|Path, int) with filename and 0-based line number. - """ - # See Item.location. - location: tuple[str, int | None, str] | None = getattr(node, "location", None) - if location is not None: - return location[:2] - obj = getattr(node, "obj", None) - if obj is not None: - return getfslineno(obj) - return getattr(node, "path", "unknown location"), -1 - - -class Collector(Node, abc.ABC): - """Base class of all collectors. - - Collector create children through `collect()` and thus iteratively build - the collection tree. - """ - - class CollectError(Exception): - """An error during collection, contains a custom message.""" - - @abc.abstractmethod - def collect(self) -> Iterable[Item | Collector]: - """Collect children (items and collectors) for this collector.""" - raise NotImplementedError("abstract") - - # TODO: This omits the style= parameter which breaks Liskov Substitution. - def repr_failure( # type: ignore[override] - self, excinfo: ExceptionInfo[BaseException] - ) -> str | TerminalRepr: - """Return a representation of a collection failure. - - :param excinfo: Exception information for the failure. - """ - if isinstance(excinfo.value, self.CollectError) and not self.config.getoption( - "fulltrace", False - ): - exc = excinfo.value - return str(exc.args[0]) - - # Respect explicit tbstyle option, but default to "short" - # (_repr_failure_py uses "long" with "fulltrace" option always). - tbstyle = self.config.getoption("tbstyle", "auto") - if tbstyle == "auto": - tbstyle = "short" - - return self._repr_failure_py(excinfo, style=tbstyle) - - def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: - if hasattr(self, "path"): - traceback = excinfo.traceback - ntraceback = traceback.cut(path=self.path) - if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - return ntraceback.filter(excinfo) - return excinfo.traceback - - -@lru_cache(maxsize=1000) -def _check_initialpaths_for_relpath( - initial_paths: frozenset[Path], path: Path -) -> str | None: - if path in initial_paths: - return "" - - for parent in path.parents: - if parent in initial_paths: - return str(path.relative_to(parent)) - - return None - - -class FSCollector(Collector, abc.ABC): - """Base class for filesystem collectors.""" - - def __init__( - self, - fspath: None = None, - path_or_parent: Path | Node | None = None, - path: Path | None = None, - name: str | None = None, - parent: Node | None = None, - config: Config | None = None, - session: Session | None = None, - nodeid: str | None = None, - ) -> None: - if path_or_parent: - if isinstance(path_or_parent, Node): - assert parent is None - parent = cast(FSCollector, path_or_parent) - elif isinstance(path_or_parent, Path): - assert path is None - path = path_or_parent - assert path is not None - - if name is None: - name = path.name - if parent is not None and parent.path != path: - try: - rel = path.relative_to(parent.path) - except ValueError: - pass - else: - name = str(rel) - name = norm_sep(name) - self.path = path - - if session is None: - assert parent is not None - session = parent.session - - if nodeid is None: - try: - nodeid = str(self.path.relative_to(session.config.rootpath)) - except ValueError: - nodeid = _check_initialpaths_for_relpath(session._initialpaths, path) - - if nodeid: - nodeid = norm_sep(nodeid) - - super().__init__( - name=name, - parent=parent, - config=config, - session=session, - nodeid=nodeid, - path=path, - ) - - @classmethod - def from_parent( - cls, - parent, - *, - fspath: None = None, - path: Path | None = None, - **kw, - ) -> Self: - """The public constructor.""" - return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) - - -class File(FSCollector, abc.ABC): - """Base class for collecting tests from a file. - - :ref:`non-python tests`. - """ - - -class Directory(FSCollector, abc.ABC): - """Base class for collecting files from a directory. - - A basic directory collector does the following: goes over the files and - sub-directories in the directory and creates collectors for them by calling - the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`, - after checking that they are not ignored using - :hook:`pytest_ignore_collect`. - - The default directory collectors are :class:`~pytest.Dir` and - :class:`~pytest.Package`. - - .. versionadded:: 8.0 - - :ref:`custom directory collectors`. - """ - - -class Item(Node, abc.ABC): - """Base class of all test invocation items. - - Note that for a single function there might be multiple test invocation items. - """ - - nextitem = None - - def __init__( - self, - name, - parent=None, - config: Config | None = None, - session: Session | None = None, - nodeid: str | None = None, - **kw, - ) -> None: - # The first two arguments are intentionally passed positionally, - # to keep plugins who define a node type which inherits from - # (pytest.Item, pytest.File) working (see issue #8435). - # They can be made kwargs when the deprecation above is done. - super().__init__( - name, - parent, - config=config, - session=session, - nodeid=nodeid, - **kw, - ) - self._report_sections: list[tuple[str, str, str]] = [] - - #: A list of tuples (name, value) that holds user defined properties - #: for this test. - self.user_properties: list[tuple[str, object]] = [] - - self._check_item_and_collector_diamond_inheritance() - - def _check_item_and_collector_diamond_inheritance(self) -> None: - """ - Check if the current type inherits from both File and Collector - at the same time, emitting a warning accordingly (#8447). - """ - cls = type(self) - - # We inject an attribute in the type to avoid issuing this warning - # for the same class more than once, which is not helpful. - # It is a hack, but was deemed acceptable in order to avoid - # flooding the user in the common case. - attr_name = "_pytest_diamond_inheritance_warning_shown" - if getattr(cls, attr_name, False): - return - setattr(cls, attr_name, True) - - problems = ", ".join( - base.__name__ for base in cls.__bases__ if issubclass(base, Collector) - ) - if problems: - warnings.warn( - f"{cls.__name__} is an Item subclass and should not be a collector, " - f"however its bases {problems} are collectors.\n" - "Please split the Collectors and the Item into separate node types.\n" - "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n" - "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/", - PytestWarning, - ) - - @abc.abstractmethod - def runtest(self) -> None: - """Run the test case for this item. - - Must be implemented by subclasses. - - .. seealso:: :ref:`non-python tests` - """ - raise NotImplementedError("runtest must be implemented by Item subclass") - - def add_report_section(self, when: str, key: str, content: str) -> None: - """Add a new report section, similar to what's done internally to add - stdout and stderr captured output:: - - item.add_report_section("call", "stdout", "report section contents") - - :param str when: - One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. - :param str key: - Name of the section, can be customized at will. Pytest uses ``"stdout"`` and - ``"stderr"`` internally. - :param str content: - The full contents as a string. - """ - if content: - self._report_sections.append((when, key, content)) - - def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: - """Get location information for this item for test reports. - - Returns a tuple with three elements: - - - The path of the test (default ``self.path``) - - The 0-based line number of the test (default ``None``) - - A name of the test to be shown (default ``""``) - - .. seealso:: :ref:`non-python tests` - """ - return self.path, None, "" - - @cached_property - def location(self) -> tuple[str, int | None, str]: - """ - Returns a tuple of ``(relfspath, lineno, testname)`` for this item - where ``relfspath`` is file path relative to ``config.rootpath`` - and lineno is a 0-based line number. - """ - location = self.reportinfo() - path = absolutepath(location[0]) - relfspath = self.session._node_location_to_relpath(path) - assert type(location[2]) is str - return (relfspath, location[1], location[2]) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/outcomes.py b/tests/venv2/lib/python3.11/site-packages/_pytest/outcomes.py deleted file mode 100644 index 257b95c..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/outcomes.py +++ /dev/null @@ -1,286 +0,0 @@ -"""Exception classes and constants handling test outcomes as well as -functions creating them.""" - -from __future__ import annotations - -import importlib -import sys -from typing import Any -from typing import ClassVar -from typing import NoReturn - - -class OutcomeException(BaseException): - """OutcomeException and its subclass instances indicate and contain info - about test and collection outcomes.""" - - def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: - if msg is not None and not isinstance(msg, str): - error_msg = ( # type: ignore[unreachable] - "{} expected string as 'msg' parameter, got '{}' instead.\n" - "Perhaps you meant to use a mark?" - ) - raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__)) - super().__init__(msg) - self.msg = msg - self.pytrace = pytrace - - def __repr__(self) -> str: - if self.msg is not None: - return self.msg - return f"<{self.__class__.__name__} instance>" - - __str__ = __repr__ - - -TEST_OUTCOME = (OutcomeException, Exception) - - -class Skipped(OutcomeException): - # XXX hackish: on 3k we fake to live in the builtins - # in order to have Skipped exception printing shorter/nicer - __module__ = "builtins" - - def __init__( - self, - msg: str | None = None, - pytrace: bool = True, - allow_module_level: bool = False, - *, - _use_item_location: bool = False, - ) -> None: - super().__init__(msg=msg, pytrace=pytrace) - self.allow_module_level = allow_module_level - # If true, the skip location is reported as the item's location, - # instead of the place that raises the exception/calls skip(). - self._use_item_location = _use_item_location - - -class Failed(OutcomeException): - """Raised from an explicit call to pytest.fail().""" - - __module__ = "builtins" - - -class Exit(Exception): - """Raised for immediate program exits (no tracebacks/summaries).""" - - def __init__( - self, msg: str = "unknown reason", returncode: int | None = None - ) -> None: - self.msg = msg - self.returncode = returncode - super().__init__(msg) - - -class XFailed(Failed): - """Raised from an explicit call to pytest.xfail().""" - - -class _Exit: - """Exit testing process. - - :param reason: - The message to show as the reason for exiting pytest. reason has a default value - only because `msg` is deprecated. - - :param returncode: - Return code to be used when exiting pytest. None means the same as ``0`` (no error), - same as :func:`sys.exit`. - - :raises pytest.exit.Exception: - The exception that is raised. - """ - - Exception: ClassVar[type[Exit]] = Exit - - def __call__(self, reason: str = "", returncode: int | None = None) -> NoReturn: - __tracebackhide__ = True - raise Exit(msg=reason, returncode=returncode) - - -exit: _Exit = _Exit() - - -class _Skip: - """Skip an executing test with the given message. - - This function should be called only during testing (setup, call or teardown) or - during collection by using the ``allow_module_level`` flag. This function can - be called in doctests as well. - - :param reason: - The message to show the user as reason for the skip. - - :param allow_module_level: - Allows this function to be called at module level. - Raising the skip exception at module level will stop - the execution of the module and prevent the collection of all tests in the module, - even those defined before the `skip` call. - - Defaults to False. - - :raises pytest.skip.Exception: - The exception that is raised. - - .. note:: - It is better to use the :ref:`pytest.mark.skipif ref` marker when - possible to declare a test to be skipped under certain conditions - like mismatching platforms or dependencies. - Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`) - to skip a doctest statically. - """ - - Exception: ClassVar[type[Skipped]] = Skipped - - def __call__(self, reason: str = "", allow_module_level: bool = False) -> NoReturn: - __tracebackhide__ = True - raise Skipped(msg=reason, allow_module_level=allow_module_level) - - -skip: _Skip = _Skip() - - -class _Fail: - """Explicitly fail an executing test with the given message. - - :param reason: - The message to show the user as reason for the failure. - - :param pytrace: - If False, msg represents the full failure information and no - python traceback will be reported. - - :raises pytest.fail.Exception: - The exception that is raised. - """ - - Exception: ClassVar[type[Failed]] = Failed - - def __call__(self, reason: str = "", pytrace: bool = True) -> NoReturn: - __tracebackhide__ = True - raise Failed(msg=reason, pytrace=pytrace) - - -fail: _Fail = _Fail() - - -class _XFail: - """Imperatively xfail an executing test or setup function with the given reason. - - This function should be called only during testing (setup, call or teardown). - - No other code is executed after using ``xfail()`` (it is implemented - internally by raising an exception). - - :param reason: - The message to show the user as reason for the xfail. - - .. note:: - It is better to use the :ref:`pytest.mark.xfail ref` marker when - possible to declare a test to be xfailed under certain conditions - like known bugs or missing features. - - :raises pytest.xfail.Exception: - The exception that is raised. - """ - - Exception: ClassVar[type[XFailed]] = XFailed - - def __call__(self, reason: str = "") -> NoReturn: - __tracebackhide__ = True - raise XFailed(msg=reason) - - -xfail: _XFail = _XFail() - - -def importorskip( - modname: str, - minversion: str | None = None, - reason: str | None = None, - *, - exc_type: type[ImportError] | None = None, -) -> Any: - """Import and return the requested module ``modname``, or skip the - current test if the module cannot be imported. - - :param modname: - The name of the module to import. - :param minversion: - If given, the imported module's ``__version__`` attribute must be at - least this minimal version, otherwise the test is still skipped. - :param reason: - If given, this reason is shown as the message when the module cannot - be imported. - :param exc_type: - The exception that should be captured in order to skip modules. - Must be :py:class:`ImportError` or a subclass. - - Defaults to :class:`ModuleNotFoundError` when not given, which means - the module must be missing for the test to be skipped. - Pass ``exc_type=ImportError`` to also skip modules that raise - :class:`ImportError` during import. - - See :ref:`import-or-skip-import-error` for details. - - - :returns: - The imported module. This should be assigned to its canonical name. - - :raises pytest.skip.Exception: - If the module cannot be imported. - - Example:: - - docutils = pytest.importorskip("docutils") - - .. versionadded:: 8.2 - - The ``exc_type`` parameter. - - .. versionchanged:: 9.1 - - The default for ``exc_type`` is now :class:`ModuleNotFoundError`. - """ - import warnings - - __tracebackhide__ = True - compile(modname, "", "eval") # to catch syntaxerrors - - # Keep the public signature compatible while using the pytest 9.1 default behavior. - if exc_type is None: - exc_type = ModuleNotFoundError - - skipped: Skipped | None = None - - with warnings.catch_warnings(): - # Make sure to ignore ImportWarnings that might happen because - # of existing directories with the same name we're trying to - # import but without a __init__.py file. - warnings.simplefilter("ignore") - - try: - importlib.import_module(modname) - except exc_type as exc: - # Do not raise or issue warnings inside the catch_warnings() block. - if reason is None: - reason = f"could not import {modname!r}: {exc}" - skipped = Skipped(reason, allow_module_level=True) - if skipped: - raise skipped - - mod = sys.modules[modname] - if minversion is None: - return mod - verattr = getattr(mod, "__version__", None) - if minversion is not None: - # Imported lazily to improve start-up time. - from packaging.version import Version - - if verattr is None or Version(verattr) < Version(minversion): - raise Skipped( - f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", - allow_module_level=True, - ) - return mod diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/pastebin.py b/tests/venv2/lib/python3.11/site-packages/_pytest/pastebin.py deleted file mode 100644 index e6a1430..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/pastebin.py +++ /dev/null @@ -1,121 +0,0 @@ -# mypy: allow-untyped-defs -"""Submit failure or test session information to a pastebin service.""" - -from __future__ import annotations - -from io import StringIO -import tempfile -from typing import IO - -from _pytest.config import Config -from _pytest.config import create_terminal_writer -from _pytest.config.argparsing import Parser -from _pytest.deprecated import PASTEBIN -from _pytest.stash import StashKey -from _pytest.terminal import TerminalReporter -import pytest - - -pastebinfile_key = StashKey[IO[bytes]]() - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting") - group.addoption( - "--pastebin", - metavar="mode", - action="store", - dest="pastebin", - default=None, - choices=["failed", "all"], - help="Send failed|all info to bpaste.net pastebin service", - ) - - -@pytest.hookimpl(trylast=True) -def pytest_configure(config: Config) -> None: - if config.option.pastebin: - config.issue_config_time_warning(PASTEBIN, 2) - - if config.option.pastebin == "all": - tr = config.pluginmanager.getplugin("terminalreporter") - # If no terminal reporter plugin is present, nothing we can do here; - # this can happen when this function executes in a worker node - # when using pytest-xdist, for example. - if tr is not None: - # pastebin file will be UTF-8 encoded binary file. - config.stash[pastebinfile_key] = tempfile.TemporaryFile("w+b") - oldwrite = tr._tw.write - - def tee_write(s, **kwargs): - oldwrite(s, **kwargs) - if isinstance(s, str): - s = s.encode("utf-8") - config.stash[pastebinfile_key].write(s) - - tr._tw.write = tee_write - - -def pytest_unconfigure(config: Config) -> None: - if pastebinfile_key in config.stash: - pastebinfile = config.stash[pastebinfile_key] - # Get terminal contents and delete file. - pastebinfile.seek(0) - sessionlog = pastebinfile.read() - pastebinfile.close() - del config.stash[pastebinfile_key] - # Undo our patching in the terminal reporter. - tr = config.pluginmanager.getplugin("terminalreporter") - del tr._tw.__dict__["write"] - # Write summary. - tr.write_sep("=", "Sending information to Paste Service") - pastebinurl = create_new_paste(sessionlog) - tr.write_line(f"pastebin session-log: {pastebinurl}\n") - - -def create_new_paste(contents: str | bytes) -> str: - """Create a new paste using the bpaste.net service. - - :contents: Paste contents string. - :returns: URL to the pasted contents, or an error message. - """ - import re - from urllib.error import HTTPError - from urllib.parse import urlencode - from urllib.request import urlopen - - params = {"code": contents, "lexer": "text", "expiry": "1week"} - url = "https://bpa.st" - try: - response: str = ( - urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") - ) - except HTTPError as e: - with e: # HTTPErrors are also http responses that must be closed! - return f"bad response: {e}" - except OSError as e: # eg urllib.error.URLError - return f"bad response: {e}" - m = re.search(r'href="/raw/(\w+)"', response) - if m: - return f"{url}/show/{m.group(1)}" - else: - return "bad response: invalid format ('" + response + "')" - - -def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: - if terminalreporter.config.option.pastebin != "failed": - return - if "failed" in terminalreporter.stats: - terminalreporter.write_sep("=", "Sending information to Paste Service") - for rep in terminalreporter.stats["failed"]: - try: - msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc - except AttributeError: - msg = terminalreporter._getfailureheadline(rep) - file = StringIO() - tw = create_terminal_writer(terminalreporter.config, file) - rep.toterminal(tw) - s = file.getvalue() - assert len(s) - pastebinurl = create_new_paste(s) - terminalreporter.write_line(f"{msg} --> {pastebinurl}") diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/pathlib.py b/tests/venv2/lib/python3.11/site-packages/_pytest/pathlib.py deleted file mode 100644 index 291bdf4..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/pathlib.py +++ /dev/null @@ -1,1054 +0,0 @@ -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -import contextlib -from enum import Enum -from errno import EBADF -from errno import ELOOP -from errno import ENOENT -from errno import ENOTDIR -import fnmatch -from functools import partial -from importlib.machinery import ModuleSpec -from importlib.machinery import PathFinder -import importlib.util -import itertools -import os -from os.path import expanduser -from os.path import expandvars -from os.path import isabs -from os.path import sep -from pathlib import Path -from pathlib import PurePath -from posixpath import sep as posix_sep -import shutil -import sys -import types -from types import ModuleType -from typing import Any -from typing import TypeVar -import uuid -import warnings - -from _pytest.compat import assert_never -from _pytest.outcomes import skip -from _pytest.warning_types import PytestWarning - - -if sys.version_info < (3, 11): - from importlib._bootstrap_external import _NamespaceLoader as NamespaceLoader -else: - from importlib.machinery import NamespaceLoader - -LOCK_TIMEOUT = 60 * 60 * 24 * 3 - -_AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath) - -# The following function, variables and comments were -# copied from cpython 3.9 Lib/pathlib.py file. - -# EBADF - guard against macOS `stat` throwing EBADF -_IGNORED_ERRORS = (ENOENT, ENOTDIR, EBADF, ELOOP) - -_IGNORED_WINERRORS = ( - 21, # ERROR_NOT_READY - drive exists but is not accessible - 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself -) - - -def _ignore_error(exception: Exception) -> bool: - return ( - getattr(exception, "errno", None) in _IGNORED_ERRORS - or getattr(exception, "winerror", None) in _IGNORED_WINERRORS - ) - - -def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: - return path.joinpath(".lock") - - -def on_rm_rf_error( - func: Callable[..., Any] | None, - path: str, - excinfo: BaseException - | tuple[type[BaseException], BaseException, types.TracebackType | None], - *, - start_path: Path, -) -> bool: - """Handle known read-only errors during rmtree. - - The returned value is used only by our own tests. - """ - if isinstance(excinfo, BaseException): - exc = excinfo - else: - exc = excinfo[1] - - # Another process removed the file in the middle of the "rm_rf" (xdist for example). - # More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018 - if isinstance(exc, FileNotFoundError): - return False - - if not isinstance(exc, PermissionError): - warnings.warn( - PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}") - ) - return False - - if func not in (os.rmdir, os.remove, os.unlink): - if func not in (os.open,): - warnings.warn( - PytestWarning( - f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}" - ) - ) - return False - - # Chmod + retry. - import stat - - def chmod_rw(p: str) -> None: - mode = os.stat(p).st_mode - os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR) - - # For files, we need to recursively go upwards in the directories to - # ensure they all are also writable. - p = Path(path) - if p.is_file(): - for parent in p.parents: - chmod_rw(str(parent)) - # Stop when we reach the original path passed to rm_rf. - if parent == start_path: - break - chmod_rw(str(path)) - - func(path) - return True - - -def ensure_extended_length_path(path: Path) -> Path: - """Get the extended-length version of a path (Windows). - - On Windows, by default, the maximum length of a path (MAX_PATH) is 260 - characters, and operations on paths longer than that fail. But it is possible - to overcome this by converting the path to "extended-length" form before - performing the operation: - https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation - - On Windows, this function returns the extended-length absolute version of path. - On other platforms it returns path unchanged. - """ - if sys.platform.startswith("win32"): - path = path.resolve() - path = Path(get_extended_length_path_str(str(path))) - return path - - -def get_extended_length_path_str(path: str) -> str: - """Convert a path to a Windows extended length path.""" - long_path_prefix = "\\\\?\\" - unc_long_path_prefix = "\\\\?\\UNC\\" - if path.startswith((long_path_prefix, unc_long_path_prefix)): - return path - # UNC - if path.startswith("\\\\"): - return unc_long_path_prefix + path[2:] - return long_path_prefix + path - - -def rm_rf(path: Path) -> None: - """Remove the path contents recursively, even if some elements - are read-only.""" - path = ensure_extended_length_path(path) - onerror = partial(on_rm_rf_error, start_path=path) - if sys.version_info >= (3, 12): - shutil.rmtree(str(path), onexc=onerror) - else: - shutil.rmtree(str(path), onerror=onerror) - - -def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]: - """Find all elements in root that begin with the prefix, case-insensitive.""" - l_prefix = prefix.lower() - for x in os.scandir(root): - if x.name.lower().startswith(l_prefix): - yield x - - -def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]: - """Return the parts of the paths following the prefix. - - :param iter: Iterator over path names. - :param prefix: Expected prefix of the path names. - """ - p_len = len(prefix) - for entry in iter: - yield entry.name[p_len:] - - -def find_suffixes(root: Path, prefix: str) -> Iterator[str]: - """Combine find_prefixes and extract_suffixes.""" - return extract_suffixes(find_prefixed(root, prefix), prefix) - - -def parse_num(maybe_num: str) -> int: - """Parse number path suffixes, returns -1 on error.""" - try: - return int(maybe_num) - except ValueError: - return -1 - - -def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None: - """Helper to create the current symlink. - - It's full of race conditions that are reasonably OK to ignore - for the context of best effort linking to the latest test run. - - The presumption being that in case of much parallelism - the inaccuracy is going to be acceptable. - """ - current_symlink = root.joinpath(target) - try: - current_symlink.unlink() - except OSError: - pass - try: - current_symlink.symlink_to(link_to) - except Exception: - pass - - -def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path: - """Create a directory with an increased number as suffix for the given prefix.""" - for i in range(10): - # try up to 10 times to create the directory - max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) - new_number = max_existing + 1 - new_path = root.joinpath(f"{prefix}{new_number}") - try: - new_path.mkdir(mode=mode) - except Exception: - pass - else: - _force_symlink(root, prefix + "current", new_path) - return new_path - else: - raise OSError( - "could not create numbered dir with prefix " - f"{prefix} in {root} after 10 tries" - ) - - -def create_cleanup_lock(p: Path) -> Path: - """Create a lock to prevent premature directory cleanup.""" - lock_path = get_lock_path(p) - try: - fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644) - except FileExistsError as e: - raise OSError(f"cannot create lockfile in {p}") from e - else: - pid = os.getpid() - spid = str(pid).encode() - os.write(fd, spid) - os.close(fd) - if not lock_path.is_file(): - raise OSError("lock path got renamed after successful creation") - return lock_path - - -def register_cleanup_lock_removal(lock_path: Path, register: Any) -> Any: - """Register a cleanup function for removing a lock.""" - pid = os.getpid() - - def cleanup_on_exit(lock_path: Path = lock_path, original_pid: int = pid) -> None: - current_pid = os.getpid() - if current_pid != original_pid: - # fork - return - try: - lock_path.unlink() - except OSError: - pass - - return register(cleanup_on_exit) - - -def maybe_delete_a_numbered_dir(path: Path) -> None: - """Remove a numbered directory if its lock can be obtained and it does - not seem to be in use.""" - path = ensure_extended_length_path(path) - lock_path = None - try: - lock_path = create_cleanup_lock(path) - parent = path.parent - - garbage = parent.joinpath(f"garbage-{uuid.uuid4()}") - path.rename(garbage) - rm_rf(garbage) - except OSError: - # known races: - # * other process did a cleanup at the same time - # * deletable directory was found - # * process cwd (Windows) - return - finally: - # If we created the lock, ensure we remove it even if we failed - # to properly remove the numbered dir. - if lock_path is not None: - try: - lock_path.unlink() - except OSError: - pass - - -def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) -> bool: - """Check if `path` is deletable based on whether the lock file is expired.""" - if path.is_symlink(): - return False - lock = get_lock_path(path) - try: - if not lock.is_file(): - return True - except OSError: - # we might not have access to the lock file at all, in this case assume - # we don't have access to the entire directory (#7491). - return False - try: - lock_time = lock.stat().st_mtime - except Exception: - return False - else: - if lock_time < consider_lock_dead_if_created_before: - # We want to ignore any errors while trying to remove the lock such as: - # - PermissionDenied, like the file permissions have changed since the lock creation; - # - FileNotFoundError, in case another pytest process got here first; - # and any other cause of failure. - with contextlib.suppress(OSError): - lock.unlink() - return True - return False - - -def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None: - """Try to cleanup a directory if we can ensure it's deletable.""" - if ensure_deletable(path, consider_lock_dead_if_created_before): - maybe_delete_a_numbered_dir(path) - - -def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]: - """List candidates for numbered directories to be removed - follows py.path.""" - max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) - max_delete = max_existing - keep - entries = find_prefixed(root, prefix) - entries, entries2 = itertools.tee(entries) - numbers = map(parse_num, extract_suffixes(entries2, prefix)) - for entry, number in zip(entries, numbers, strict=True): - if number <= max_delete: - yield Path(entry) - - -def cleanup_dead_symlinks(root: Path) -> None: - for left_dir in root.iterdir(): - if left_dir.is_symlink(): - if not left_dir.resolve().exists(): - left_dir.unlink() - - -def cleanup_numbered_dir( - root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float -) -> None: - """Cleanup for lock driven numbered directories.""" - if not root.exists(): - return - for path in cleanup_candidates(root, prefix, keep): - try_cleanup(path, consider_lock_dead_if_created_before) - for path in root.glob("garbage-*"): - try_cleanup(path, consider_lock_dead_if_created_before) - - cleanup_dead_symlinks(root) - - -def make_numbered_dir_with_cleanup( - *, - root: Path, - prefix: str, - mode: int, - keep: int, - lock_timeout: float, - register: Any, -) -> Path: - """Create a numbered dir and register its cleanup. - - Similar to make_numbered_dir, but also maintains a lock file indicating that - the directory is currently in use, and registers the cleanup of the lock and - of stale numbered directories. - - :param keep: - The number of sessions to retain the directory. - :param lock_timeout: - In case of a crash, the lock remains "stuck". The timeout is a time - limit after which the lock is considered stale and can be removed. - :param register: - Called as register(cleanup_func, params...). Should schedule to call - passed cleanup functions on session finish. - """ - e = None - for i in range(10): - try: - p = make_numbered_dir(root, prefix, mode) - # Only lock the current dir when keep is not 0 - if keep != 0: - lock_path = create_cleanup_lock(p) - register_cleanup_lock_removal(lock_path, register) - except Exception as exc: - e = exc - else: - consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout - # Register a cleanup for program exit - register( - cleanup_numbered_dir, - root, - prefix, - keep, - consider_lock_dead_if_created_before, - ) - return p - assert e is not None - raise e - - -def resolve_from_str(input: str, rootpath: Path) -> Path: - input = expanduser(input) - input = expandvars(input) - if isabs(input): - return Path(input) - else: - return rootpath.joinpath(input) - - -def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: - """A port of FNMatcher from py.path.common which works with PurePath() instances. - - The difference between this algorithm and PurePath.match() is that the - latter matches "**" glob expressions for each part of the path, while - this algorithm uses the whole path instead. - - For example: - "tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" - with this algorithm, but not with PurePath.match(). - - This algorithm was ported to keep backward-compatibility with existing - settings which assume paths match according this logic. - - References: - * https://bugs.python.org/issue29249 - * https://bugs.python.org/issue34731 - """ - path = PurePath(path) - iswin32 = sys.platform.startswith("win") - - if iswin32 and sep not in pattern and posix_sep in pattern: - # Running on Windows, the pattern has no Windows path separators, - # and the pattern has one or more Posix path separators. Replace - # the Posix path separators with the Windows path separator. - pattern = pattern.replace(posix_sep, sep) - - if sep not in pattern: - name = path.name - else: - name = str(path) - if path.is_absolute() and not os.path.isabs(pattern): - pattern = f"*{os.sep}{pattern}" - return fnmatch.fnmatch(name, pattern) - - -def parts(s: str) -> set[str]: - parts = s.split(sep) - return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} - - -def symlink_or_skip( - src: os.PathLike[str] | str, - dst: os.PathLike[str] | str, - **kwargs: Any, -) -> None: - """Make a symlink, or skip the test in case symlinks are not supported.""" - try: - os.symlink(src, dst, **kwargs) - except OSError as e: - skip(f"symlinks not supported: {e}") - - -class ImportMode(Enum): - """Possible values for `mode` parameter of `import_path`.""" - - prepend = "prepend" - append = "append" - importlib = "importlib" - - -class ImportPathMismatchError(ImportError): - """Raised on import_path() if there is a mismatch of __file__'s. - - This can happen when `import_path` is called multiple times with different filenames that has - the same basename but reside in packages - (for example "/tests1/test_foo.py" and "/tests2/test_foo.py"). - """ - - -def import_path( - path: str | os.PathLike[str], - *, - mode: str | ImportMode = ImportMode.prepend, - root: Path, - consider_namespace_packages: bool, -) -> ModuleType: - """ - Import and return a module from the given path, which can be a file (a module) or - a directory (a package). - - :param path: - Path to the file to import. - - :param mode: - Controls the underlying import mechanism that will be used: - - * ImportMode.prepend: the directory containing the module (or package, taking - `__init__.py` files into account) will be put at the *start* of `sys.path` before - being imported with `importlib.import_module`. - - * ImportMode.append: same as `prepend`, but the directory will be appended - to the end of `sys.path`, if not already in `sys.path`. - - * ImportMode.importlib: uses more fine control mechanisms provided by `importlib` - to import the module, which avoids having to muck with `sys.path` at all. It effectively - allows having same-named test modules in different places. - - :param root: - Used as an anchor when mode == ImportMode.importlib to obtain - a unique name for the module being imported so it can safely be stored - into ``sys.modules``. - - :param consider_namespace_packages: - If True, consider namespace packages when resolving module names. - - :raises ImportPathMismatchError: - If after importing the given `path` and the module `__file__` - are different. Only raised in `prepend` and `append` modes. - """ - path = Path(path) - mode = ImportMode(mode) - - if not path.exists(): - raise ImportError(path) - - if mode is ImportMode.importlib: - # Try to import this module using the standard import mechanisms, but - # without touching sys.path. - try: - _, module_name = resolve_pkg_root_and_module_name( - path, consider_namespace_packages=consider_namespace_packages - ) - except CouldNotResolvePathError: - pass - else: - # If the given module name is already in sys.modules, do not import it again. - with contextlib.suppress(KeyError): - return sys.modules[module_name] - - mod = _import_module_using_spec(module_name, path, insert_modules=False) - if mod is not None: - return mod - - # Could not import the module with the current sys.path, so we fall back - # to importing the file as a single module, not being a part of a package. - module_name = module_name_from_path(path, root) - with contextlib.suppress(KeyError): - return sys.modules[module_name] - - mod = _import_module_using_spec(module_name, path, insert_modules=True) - if mod is None: - raise ImportError(f"Can't find module {module_name} at location {path}") - return mod - - try: - pkg_root, module_name = resolve_pkg_root_and_module_name( - path, consider_namespace_packages=consider_namespace_packages - ) - except CouldNotResolvePathError: - pkg_root, module_name = path.parent, path.stem - - # Change sys.path permanently: restoring it at the end of this function would cause surprising - # problems because of delayed imports: for example, a conftest.py file imported by this function - # might have local imports, which would fail at runtime if we restored sys.path. - if mode is ImportMode.append: - if str(pkg_root) not in sys.path: - sys.path.append(str(pkg_root)) - elif mode is ImportMode.prepend: - if str(pkg_root) != sys.path[0]: - sys.path.insert(0, str(pkg_root)) - else: - assert_never(mode) - - importlib.import_module(module_name) - - mod = sys.modules[module_name] - if path.name == "__init__.py": - return mod - - ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "") - if ignore != "1": - module_file = mod.__file__ - if module_file is None: - raise ImportPathMismatchError(module_name, module_file, path) - - if module_file.endswith((".pyc", ".pyo")): - module_file = module_file[:-1] - if module_file.endswith(os.sep + "__init__.py"): - module_file = module_file[: -(len(os.sep + "__init__.py"))] - - try: - is_same = _is_same(str(path), module_file) - except FileNotFoundError: - is_same = False - - if not is_same: - raise ImportPathMismatchError(module_name, module_file, path) - - return mod - - -def _import_module_using_spec( - module_name: str, module_path: Path, *, insert_modules: bool -) -> ModuleType | None: - """ - Tries to import a module by its canonical name, path, and its parent location. - - :param module_name: - The expected module name, will become the key of `sys.modules`. - - :param module_path: - The file path of the module, for example `/foo/bar/test_demo.py`. - If module is a package, pass the path to the `__init__.py` of the package. - If module is a namespace package, pass directory path. - - :param insert_modules: - If True, will call `insert_missing_modules` to create empty intermediate modules - with made-up module names (when importing test files not reachable from `sys.path`). - - Example 1 of parent_module_*: - - module_name: "a.b.c.demo" - module_path: Path("a/b/c/demo.py") - if "a.b.c" is package ("a/b/c/__init__.py" exists), then - parent_module_name: "a.b.c" - parent_module_path: Path("a/b/c/__init__.py") - else: - parent_module_name: "a.b.c" - parent_module_path: Path("a/b/c") - - Example 2 of parent_module_*: - - module_name: "a.b.c" - module_path: Path("a/b/c/__init__.py") - if "a.b" is package ("a/b/__init__.py" exists), then - parent_module_name: "a.b" - parent_module_path: Path("a/b/__init__.py") - else: - parent_module_name: "a.b" - parent_module_path: Path("a/b/") - """ - # Attempt to import the parent module, seems is our responsibility: - # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 - parent_module_name, _, name = module_name.rpartition(".") - parent_module: ModuleType | None = None - if parent_module_name: - parent_module = sys.modules.get(parent_module_name) - # If the parent_module lacks the `__path__` attribute, AttributeError when finding a submodule's spec, - # requiring re-import according to the path. - need_reimport = not hasattr(parent_module, "__path__") - if parent_module is None or need_reimport: - # Get parent_location based on location, get parent_path based on path. - if module_path.name == "__init__.py": - # If the current module is in a package, - # need to leave the package first and then enter the parent module. - parent_module_path = module_path.parent.parent - else: - parent_module_path = module_path.parent - - if (parent_module_path / "__init__.py").is_file(): - # If the parent module is a package, loading by __init__.py file. - parent_module_path = parent_module_path / "__init__.py" - - parent_module = _import_module_using_spec( - parent_module_name, - parent_module_path, - insert_modules=insert_modules, - ) - - # Checking with sys.meta_path first in case one of its hooks can import this module, - # such as our own assertion-rewrite hook. - find_spec_path = [str(module_path.parent)] - for meta_importer in sys.meta_path: - spec = meta_importer.find_spec(module_name, find_spec_path) - - if spec_matches_module_path(spec, module_path): - break - else: - loader = None - if module_path.is_dir(): - # The `spec_from_file_location` matches a loader based on the file extension by default. - # For a namespace package, need to manually specify a loader. - loader = NamespaceLoader(name, module_path, PathFinder()) # type: ignore[arg-type] - - spec = importlib.util.spec_from_file_location( - module_name, str(module_path), loader=loader - ) - - if spec_matches_module_path(spec, module_path): - assert spec is not None - # Find spec and import this module. - mod = importlib.util.module_from_spec(spec) - sys.modules[module_name] = mod - spec.loader.exec_module(mod) # type: ignore[union-attr] - - # Set this module as an attribute of the parent module (#12194). - if parent_module is not None: - setattr(parent_module, name, mod) - - if insert_modules: - insert_missing_modules(sys.modules, module_name) - return mod - - return None - - -def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool: - """Return true if the given ModuleSpec can be used to import the given module path.""" - if module_spec is None: - return False - - if module_spec.origin: - return Path(module_spec.origin) == module_path - - # Compare the path with the `module_spec.submodule_Search_Locations` in case - # the module is part of a namespace package. - # https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec.submodule_search_locations - if module_spec.submodule_search_locations: # can be None. - for path in module_spec.submodule_search_locations: - if Path(path) == module_path: - return True - - return False - - -# Implement a special _is_same function on Windows which returns True if the two filenames -# compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678). -if sys.platform.startswith("win"): - - def _is_same(f1: str, f2: str) -> bool: - return Path(f1) == Path(f2) or os.path.samefile(f1, f2) - -else: - - def _is_same(f1: str, f2: str) -> bool: - return os.path.samefile(f1, f2) - - -def module_name_from_path(path: Path, root: Path) -> str: - """ - Return a dotted module name based on the given path, anchored on root. - - For example: path="projects/src/tests/test_foo.py" and root="/projects", the - resulting module name will be "src.tests.test_foo". - """ - path = path.with_suffix("") - try: - relative_path = path.relative_to(root) - except ValueError: - # If we can't get a relative path to root, use the full path, except - # for the first part ("d:\\" or "/" depending on the platform, for example). - path_parts = path.parts[1:] - else: - # Use the parts for the relative path to the root path. - path_parts = relative_path.parts - - # Module name for packages do not contain the __init__ file, unless - # the `__init__.py` file is at the root. - if len(path_parts) >= 2 and path_parts[-1] == "__init__": - path_parts = path_parts[:-1] - - # Module names cannot contain ".", normalize them to "_". This prevents - # a directory having a "." in the name (".env.310" for example) causing extra intermediate modules. - # Also, important to replace "." at the start of paths, as those are considered relative imports. - path_parts = tuple(x.replace(".", "_") for x in path_parts) - - return ".".join(path_parts) - - -def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: - """ - Used by ``import_path`` to create intermediate modules when using mode=importlib. - - When we want to import a module as "src.tests.test_foo" for example, we need - to create empty modules "src" and "src.tests" after inserting "src.tests.test_foo", - otherwise "src.tests.test_foo" is not importable by ``__import__``. - """ - module_parts = module_name.split(".") - while module_name: - parent_module_name, _, child_name = module_name.rpartition(".") - if parent_module_name: - parent_module = modules.get(parent_module_name) - if parent_module is None: - try: - # If sys.meta_path is empty, calling import_module will issue - # a warning and raise ModuleNotFoundError. To avoid the - # warning, we check sys.meta_path explicitly and raise the error - # ourselves to fall back to creating a dummy module. - if not sys.meta_path: - raise ModuleNotFoundError - parent_module = importlib.import_module(parent_module_name) - except ModuleNotFoundError: - parent_module = ModuleType( - module_name, - doc="Empty module created by pytest's importmode=importlib.", - ) - modules[parent_module_name] = parent_module - - # Add child attribute to the parent that can reference the child - # modules. - if not hasattr(parent_module, child_name): - setattr(parent_module, child_name, modules[module_name]) - - module_parts.pop(-1) - module_name = ".".join(module_parts) - - -def resolve_package_path(path: Path) -> Path | None: - """Return the Python package path by looking for the last - directory upwards which still contains an __init__.py. - - Returns None if it cannot be determined. - """ - result = None - for parent in itertools.chain((path,), path.parents): - if parent.is_dir(): - if not (parent / "__init__.py").is_file(): - break - if not parent.name.isidentifier(): - break - result = parent - return result - - -def resolve_pkg_root_and_module_name( - path: Path, *, consider_namespace_packages: bool = False -) -> tuple[Path, str]: - """ - Return the path to the directory of the root package that contains the - given Python file, and its module name: - - src/ - app/ - __init__.py - core/ - __init__.py - models.py - - Passing the full path to `models.py` will yield Path("src") and "app.core.models". - - If consider_namespace_packages is True, then we additionally check upwards in the hierarchy - for namespace packages: - - https://packaging.python.org/en/latest/guides/packaging-namespace-packages - - Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files). - """ - pkg_root: Path | None = None - pkg_path = resolve_package_path(path) - if pkg_path is not None: - pkg_root = pkg_path.parent - if consider_namespace_packages: - start = pkg_root if pkg_root is not None else path.parent - for candidate in (start, *start.parents): - module_name = compute_module_name(candidate, path) - if module_name and is_importable(module_name, path): - # Point the pkg_root to the root of the namespace package. - pkg_root = candidate - break - - if pkg_root is not None: - module_name = compute_module_name(pkg_root, path) - if module_name: - return pkg_root, module_name - - raise CouldNotResolvePathError(f"Could not resolve for {path}") - - -def is_importable(module_name: str, module_path: Path) -> bool: - """ - Return if the given module path could be imported normally by Python, akin to the user - entering the REPL and importing the corresponding module name directly, and corresponds - to the module_path specified. - - :param module_name: - Full module name that we want to check if is importable. - For example, "app.models". - - :param module_path: - Full path to the python module/package we want to check if is importable. - For example, "/projects/src/app/models.py". - """ - try: - # Note this is different from what we do in ``_import_module_using_spec``, where we explicitly search through - # sys.meta_path to be able to pass the path of the module that we want to import (``meta_importer.find_spec``). - # Using importlib.util.find_spec() is different, it gives the same results as trying to import - # the module normally in the REPL. - spec = importlib.util.find_spec(module_name) - except (ImportError, ValueError, ImportWarning): - return False - else: - return spec_matches_module_path(spec, module_path) - - -def compute_module_name(root: Path, module_path: Path) -> str | None: - """Compute a module name based on a path and a root anchor.""" - try: - path_without_suffix = module_path.with_suffix("") - except ValueError: - # Empty paths (such as Path.cwd()) might break meta_path hooks (like our own assertion rewriter). - return None - - try: - relative = path_without_suffix.relative_to(root) - except ValueError: # pragma: no cover - return None - names = list(relative.parts) - if not names: - return None - if names[-1] == "__init__": - names.pop() - return ".".join(names) - - -class CouldNotResolvePathError(Exception): - """Custom exception raised by resolve_pkg_root_and_module_name.""" - - -def scandir( - path: str | os.PathLike[str], - sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name, -) -> list[os.DirEntry[str]]: - """Scan a directory recursively, in breadth-first order. - - The returned entries are sorted according to the given key. - The default is to sort by name. - If the directory does not exist, return an empty list. - """ - entries = [] - # Attempt to create a scandir iterator for the given path. - try: - scandir_iter = os.scandir(path) - except FileNotFoundError: - # If the directory does not exist, return an empty list. - return [] - # Use the scandir iterator in a context manager to ensure it is properly closed. - with scandir_iter as s: - for entry in s: - try: - entry.is_file() - except OSError as err: - if _ignore_error(err): - continue - # Reraise non-ignorable errors to avoid hiding issues. - raise - entries.append(entry) - entries.sort(key=sort_key) # type: ignore[arg-type] - return entries - - -def visit( - path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool] -) -> Iterator[os.DirEntry[str]]: - """Walk a directory recursively, in breadth-first order. - - The `recurse` predicate determines whether a directory is recursed. - - Entries at each directory level are sorted. - """ - entries = scandir(path) - yield from entries - for entry in entries: - if entry.is_dir() and recurse(entry): - yield from visit(entry.path, recurse) - - -def absolutepath(path: str | os.PathLike[str]) -> Path: - """Convert a path to an absolute path using os.path.abspath. - - Prefer this over Path.resolve() (see #6523). - Prefer this over Path.absolute() (not public, doesn't normalize). - """ - return Path(os.path.abspath(path)) - - -def commonpath(path1: Path, path2: Path) -> Path | None: - """Return the common part shared with the other path, or None if there is - no common part. - - If one path is relative and one is absolute, returns None. - """ - try: - return Path(os.path.commonpath((str(path1), str(path2)))) - except ValueError: - return None - - -def bestrelpath(directory: Path, dest: Path) -> str: - """Return a string which is a relative path from directory to dest such - that directory/bestrelpath == dest. - - The paths must be either both absolute or both relative. - - If no such path can be determined, returns dest. - """ - assert isinstance(directory, Path) - assert isinstance(dest, Path) - if dest == directory: - return os.curdir - # Find the longest common directory. - base = commonpath(directory, dest) - # Can be the case on Windows for two absolute paths on different drives. - # Can be the case for two relative paths without common prefix. - # Can be the case for a relative path and an absolute path. - if not base: - return str(dest) - reldirectory = directory.relative_to(base) - reldest = dest.relative_to(base) - return os.path.join( - # Back from directory to base. - *([os.pardir] * len(reldirectory.parts)), - # Forward from base to dest. - *reldest.parts, - ) - - -def safe_exists(p: Path) -> bool: - """Like Path.exists(), but account for input arguments that might be too long (#11394).""" - try: - return p.exists() - except (ValueError, OSError): - # ValueError: stat: path too long for Windows - # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect - return False - - -def samefile_nofollow(p1: Path, p2: Path) -> bool: - """Test whether two paths reference the same actual file or directory. - - Unlike Path.samefile(), does not resolve symlinks. - """ - return os.path.samestat(p1.lstat(), p2.lstat()) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/py.typed b/tests/venv2/lib/python3.11/site-packages/_pytest/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/pytester.py b/tests/venv2/lib/python3.11/site-packages/_pytest/pytester.py deleted file mode 100644 index b69b587..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/pytester.py +++ /dev/null @@ -1,1803 +0,0 @@ -# mypy: allow-untyped-defs -"""(Disabled by default) support for testing pytest and pytest plugins. - -PYTEST_DONT_REWRITE -""" - -from __future__ import annotations - -import collections.abc -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Mapping -from collections.abc import Sequence -import contextlib -from fnmatch import fnmatch -import gc -import importlib -from io import StringIO -import locale -import os -from pathlib import Path -import platform -import re -import shutil -import subprocess -import sys -import traceback -from typing import Any -from typing import Final -from typing import final -from typing import IO -from typing import Literal -from typing import overload -from typing import TextIO -from typing import TYPE_CHECKING -from weakref import WeakKeyDictionary - -from iniconfig import IniConfig -from iniconfig import SectionWrapper - -from _pytest import timing -from _pytest._code import Source -from _pytest.capture import _get_multicapture -from _pytest.compat import NOTSET -from _pytest.compat import NotSetType -from _pytest.config import _PluggyPlugin -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config import main -from _pytest.config import PytestPluginManager -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import FixtureRequest -from _pytest.main import Session -from _pytest.monkeypatch import MonkeyPatch -from _pytest.nodes import Collector -from _pytest.nodes import Item -from _pytest.outcomes import fail -from _pytest.outcomes import importorskip -from _pytest.outcomes import skip -from _pytest.pathlib import bestrelpath -from _pytest.pathlib import make_numbered_dir -from _pytest.reports import CollectReport -from _pytest.reports import TestReport -from _pytest.tmpdir import TempPathFactory -from _pytest.warning_types import PytestFDWarning - - -if TYPE_CHECKING: - import pexpect - - -pytest_plugins = ["pytester_assertions"] - - -IGNORE_PAM = [ # filenames added when obtaining details about the current user - "/var/lib/sss/mc/passwd" -] - - -def pytest_addoption(parser: Parser) -> None: - parser.addoption( - "--lsof", - action="store_true", - dest="lsof", - default=False, - help="Run FD checks if lsof is available", - ) - - parser.addoption( - "--runpytest", - default="inprocess", - dest="runpytest", - choices=("inprocess", "subprocess"), - help=( - "Run pytest sub runs in tests using an 'inprocess' " - "or 'subprocess' (python -m main) method" - ), - ) - - parser.addini( - "pytester_example_dir", help="Directory to take the pytester example files from" - ) - - -def pytest_configure(config: Config) -> None: - if config.getvalue("lsof"): - checker = LsofFdLeakChecker() - if checker.matching_platform(): - config.pluginmanager.register(checker) - - config.addinivalue_line( - "markers", - "pytester_example_path(*path_segments): join the given path " - "segments to `pytester_example_dir` for this test.", - ) - - -class LsofFdLeakChecker: - def get_open_files(self) -> list[tuple[str, str]]: - if sys.version_info >= (3, 11): - # New in Python 3.11, ignores utf-8 mode - encoding = locale.getencoding() - else: - encoding = locale.getpreferredencoding(False) - out = subprocess.run( - ("lsof", "-Ffn0", "-p", str(os.getpid())), - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - check=True, - text=True, - encoding=encoding, - ).stdout - - def isopen(line: str) -> bool: - return line.startswith("f") and ( - "deleted" not in line - and "mem" not in line - and "txt" not in line - and "cwd" not in line - ) - - open_files = [] - - for line in out.split("\n"): - if isopen(line): - fields = line.split("\0") - fd = fields[0][1:] - filename = fields[1][1:] - if filename in IGNORE_PAM: - continue - if filename.startswith("/"): - open_files.append((fd, filename)) - - return open_files - - def matching_platform(self) -> bool: - try: - subprocess.run(("lsof", "-v"), check=True) - except (OSError, subprocess.CalledProcessError): - return False - else: - return True - - @hookimpl(wrapper=True, tryfirst=True) - def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]: - lines1 = self.get_open_files() - try: - return (yield) - finally: - if hasattr(sys, "pypy_version_info"): - gc.collect() - lines2 = self.get_open_files() - - new_fds = {t[0] for t in lines2} - {t[0] for t in lines1} - leaked_files = [t for t in lines2 if t[0] in new_fds] - if leaked_files: - error = [ - f"***** {len(leaked_files)} FD leakage detected", - *(str(f) for f in leaked_files), - "*** Before:", - *(str(f) for f in lines1), - "*** After:", - *(str(f) for f in lines2), - f"***** {len(leaked_files)} FD leakage detected", - "*** function {}:{}: {} ".format(*item.location), - "See issue #2366", - ] - item.warn(PytestFDWarning("\n".join(error))) - - -# used at least by pytest-xdist plugin - - -@fixture -def _pytest(request: FixtureRequest) -> PytestArg: - """Return a helper which offers a gethookrecorder(hook) method which - returns a HookRecorder instance which helps to make assertions about called - hooks.""" - return PytestArg(request) - - -class PytestArg: - def __init__(self, request: FixtureRequest) -> None: - self._request = request - - def gethookrecorder(self, hook) -> HookRecorder: - hookrecorder = HookRecorder(hook._pm) - self._request.addfinalizer(hookrecorder.finish_recording) - return hookrecorder - - -def get_public_names(values: Iterable[str]) -> list[str]: - """Only return names from iterator values without a leading underscore.""" - return [x for x in values if x[0] != "_"] - - -@final -class RecordedHookCall: - """A recorded call to a hook. - - The arguments to the hook call are set as attributes. - For example: - - .. code-block:: python - - calls = hook_recorder.getcalls("pytest_runtest_setup") - # Suppose pytest_runtest_setup was called once with `item=an_item`. - assert calls[0].item is an_item - """ - - def __init__(self, name: str, kwargs) -> None: - self.__dict__.update(kwargs) - self._name = name - - def __repr__(self) -> str: - d = self.__dict__.copy() - del d["_name"] - return f"" - - if TYPE_CHECKING: - # The class has undetermined attributes, this tells mypy about it. - def __getattr__(self, key: str): ... - - -@final -class HookRecorder: - """Record all hooks called in a plugin manager. - - Hook recorders are created by :class:`Pytester`. - - This wraps all the hook calls in the plugin manager, recording each call - before propagating the normal calls. - """ - - def __init__( - self, pluginmanager: PytestPluginManager, *, _ispytest: bool = False - ) -> None: - check_ispytest(_ispytest) - - self._pluginmanager = pluginmanager - self.calls: list[RecordedHookCall] = [] - self.ret: int | ExitCode | None = None - - def before(hook_name: str, hook_impls, kwargs) -> None: - self.calls.append(RecordedHookCall(hook_name, kwargs)) - - def after(outcome, hook_name: str, hook_impls, kwargs) -> None: - pass - - self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after) - - def finish_recording(self) -> None: - self._undo_wrapping() - - def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]: - """Get all recorded calls to hooks with the given names (or name).""" - if isinstance(names, str): - names = names.split() - return [call for call in self.calls if call._name in names] - - def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: - __tracebackhide__ = True - i = 0 - entries = list(entries) - # Since Python 3.13, f_locals is not a dict, but eval requires a dict. - backlocals = dict(sys._getframe(1).f_locals) - while entries: - name, check = entries.pop(0) - for ind, call in enumerate(self.calls[i:]): - if call._name == name: - print("NAMEMATCH", name, call) - if eval(check, backlocals, call.__dict__): - print("CHECKERMATCH", repr(check), "->", call) - else: - print("NOCHECKERMATCH", repr(check), "-", call) - continue - i += ind + 1 - break - print("NONAMEMATCH", name, "with", call) - else: - fail(f"could not find {name!r} check {check!r}") - - def popcall(self, name: str) -> RecordedHookCall: - __tracebackhide__ = True - for i, call in enumerate(self.calls): - if call._name == name: - del self.calls[i] - return call - lines = [f"could not find call {name!r}, in:"] - lines.extend([f" {x}" for x in self.calls]) - fail("\n".join(lines)) - - def getcall(self, name: str) -> RecordedHookCall: - values = self.getcalls(name) - assert len(values) == 1, (name, values) - return values[0] - - # functionality for test reports - - @overload - def getreports( - self, - names: Literal["pytest_collectreport"], - ) -> Sequence[CollectReport]: ... - - @overload - def getreports( - self, - names: Literal["pytest_runtest_logreport"], - ) -> Sequence[TestReport]: ... - - @overload - def getreports( - self, - names: str | Iterable[str] = ( - "pytest_collectreport", - "pytest_runtest_logreport", - ), - ) -> Sequence[CollectReport | TestReport]: ... - - def getreports( - self, - names: str | Iterable[str] = ( - "pytest_collectreport", - "pytest_runtest_logreport", - ), - ) -> Sequence[CollectReport | TestReport]: - return [x.report for x in self.getcalls(names)] - - def matchreport( - self, - inamepart: str = "", - names: str | Iterable[str] = ( - "pytest_runtest_logreport", - "pytest_collectreport", - ), - when: str | None = None, - ) -> CollectReport | TestReport: - """Return a testreport whose dotted import path matches.""" - values = [] - for rep in self.getreports(names=names): - if not when and rep.when != "call" and rep.passed: - # setup/teardown passing reports - let's ignore those - continue - if when and rep.when != when: - continue - if not inamepart or inamepart in rep.nodeid.split("::"): - values.append(rep) - if not values: - raise ValueError( - f"could not find test report matching {inamepart!r}: " - "no test reports at all!" - ) - if len(values) > 1: - raise ValueError( - f"found 2 or more testreports matching {inamepart!r}: {values}" - ) - return values[0] - - @overload - def getfailures( - self, - names: Literal["pytest_collectreport"], - ) -> Sequence[CollectReport]: ... - - @overload - def getfailures( - self, - names: Literal["pytest_runtest_logreport"], - ) -> Sequence[TestReport]: ... - - @overload - def getfailures( - self, - names: str | Iterable[str] = ( - "pytest_collectreport", - "pytest_runtest_logreport", - ), - ) -> Sequence[CollectReport | TestReport]: ... - - def getfailures( - self, - names: str | Iterable[str] = ( - "pytest_collectreport", - "pytest_runtest_logreport", - ), - ) -> Sequence[CollectReport | TestReport]: - return [rep for rep in self.getreports(names) if rep.failed] - - def getfailedcollections(self) -> Sequence[CollectReport]: - return self.getfailures("pytest_collectreport") - - def listoutcomes( - self, - ) -> tuple[ - Sequence[TestReport], - Sequence[CollectReport | TestReport], - Sequence[CollectReport | TestReport], - ]: - passed = [] - skipped = [] - failed = [] - for rep in self.getreports( - ("pytest_collectreport", "pytest_runtest_logreport") - ): - if rep.passed: - if rep.when == "call": - assert isinstance(rep, TestReport) - passed.append(rep) - elif rep.skipped: - skipped.append(rep) - else: - assert rep.failed, f"Unexpected outcome: {rep!r}" - failed.append(rep) - return passed, skipped, failed - - def countoutcomes(self) -> list[int]: - return [len(x) for x in self.listoutcomes()] - - def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: - __tracebackhide__ = True - from _pytest.pytester_assertions import assertoutcome - - outcomes = self.listoutcomes() - assertoutcome( - outcomes, - passed=passed, - skipped=skipped, - failed=failed, - ) - - def clear(self) -> None: - self.calls[:] = [] - - -@fixture -def linecomp() -> LineComp: - """A :class: `LineComp` instance for checking that an input linearly - contains a sequence of strings.""" - return LineComp() - - -@fixture(name="LineMatcher") -def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: - """A reference to the :class: `LineMatcher`. - - This is instantiable with a list of lines (without their trailing newlines). - This is useful for testing large texts, such as the output of commands. - """ - return LineMatcher - - -@fixture -def pytester( - request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch -) -> Pytester: - """ - Facilities to write tests/configuration files, execute pytest in isolation, and match - against expected output, perfect for black-box testing of pytest plugins. - - It attempts to isolate the test run from external factors as much as possible, modifying - the current working directory to ``path`` and environment variables during initialization. - - It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path` - fixture but provides methods which aid in testing pytest itself. - """ - return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True) - - -@fixture -def _sys_snapshot() -> Generator[None]: - snappaths = SysPathsSnapshot() - snapmods = SysModulesSnapshot() - yield - snapmods.restore() - snappaths.restore() - - -@fixture -def _config_for_test() -> Generator[Config]: - from _pytest.config import get_config - - config = get_config() - yield config - config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles. - - -# Regex to match the session duration string in the summary: "74.34s". -rex_session_duration = re.compile(r"\d+\.\d\ds") -# Regex to match all the counts and phrases in the summary line: "34 passed, 111 skipped". -rex_outcome = re.compile(r"(\d+) (\w+)") - - -@final -class RunResult: - """The result of running a command from :class:`~pytest.Pytester`.""" - - def __init__( - self, - ret: int | ExitCode, - outlines: list[str], - errlines: list[str], - duration: float, - ) -> None: - try: - self.ret: int | ExitCode = ExitCode(ret) - """The return value.""" - except ValueError: - self.ret = ret - self.outlines = outlines - """List of lines captured from stdout.""" - self.errlines = errlines - """List of lines captured from stderr.""" - self.stdout = LineMatcher(outlines) - """:class:`~pytest.LineMatcher` of stdout. - - Use e.g. :func:`str(stdout) ` to reconstruct stdout, or the commonly used - :func:`stdout.fnmatch_lines() ` method. - """ - self.stderr = LineMatcher(errlines) - """:class:`~pytest.LineMatcher` of stderr.""" - self.duration = duration - """Duration in seconds.""" - - def __repr__(self) -> str: - return ( - f"" - ) - - def parseoutcomes(self) -> dict[str, int]: - """Return a dictionary of outcome noun -> count from parsing the terminal - output that the test process produced. - - The returned nouns will always be in plural form:: - - ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== - - Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. - """ - return self.parse_summary_nouns(self.outlines) - - @classmethod - def parse_summary_nouns(cls, lines) -> dict[str, int]: - """Extract the nouns from a pytest terminal summary line. - - It always returns the plural noun for consistency:: - - ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== - - Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. - """ - for line in reversed(lines): - if rex_session_duration.search(line): - outcomes = rex_outcome.findall(line) - ret = {noun: int(count) for (count, noun) in outcomes} - break - else: - raise ValueError("Pytest terminal summary report not found") - - to_plural = { - "warning": "warnings", - "error": "errors", - } - return {to_plural.get(k, k): v for k, v in ret.items()} - - def assert_outcomes( - self, - passed: int = 0, - skipped: int = 0, - failed: int = 0, - errors: int = 0, - xpassed: int = 0, - xfailed: int = 0, - warnings: int | None = None, - deselected: int | None = None, - ) -> None: - """ - Assert that the specified outcomes appear with the respective - numbers (0 means it didn't occur) in the text output from a test run. - - ``warnings`` and ``deselected`` are only checked if not None. - """ - __tracebackhide__ = True - from _pytest.pytester_assertions import assert_outcomes - - outcomes = self.parseoutcomes() - assert_outcomes( - outcomes, - passed=passed, - skipped=skipped, - failed=failed, - errors=errors, - xpassed=xpassed, - xfailed=xfailed, - warnings=warnings, - deselected=deselected, - ) - - -class SysModulesSnapshot: - def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: - self.__preserve = preserve - self.__saved = dict(sys.modules) - - def restore(self) -> None: - if self.__preserve: - self.__saved.update( - (k, m) for k, m in sys.modules.items() if self.__preserve(k) - ) - sys.modules.clear() - sys.modules.update(self.__saved) - - -class SysPathsSnapshot: - def __init__(self) -> None: - self.__saved = list(sys.path), list(sys.meta_path) - - def restore(self) -> None: - sys.path[:], sys.meta_path[:] = self.__saved - - -_FileContent = tuple[str | bytes, ...] | list[str | bytes] | str | bytes - - -@final -class Pytester: - """ - Facilities to write tests/configuration files, execute pytest in isolation, and match - against expected output, perfect for black-box testing of pytest plugins. - - It attempts to isolate the test run from external factors as much as possible, modifying - the current working directory to :attr:`path` and environment variables during initialization. - """ - - __test__ = False - - CLOSE_STDIN: Final = NOTSET - - class TimeoutExpired(Exception): - pass - - def __init__( - self, - request: FixtureRequest, - tmp_path_factory: TempPathFactory, - monkeypatch: MonkeyPatch, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self._request = request - self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( - WeakKeyDictionary() - ) - if request.function: - name: str = request.function.__name__ - else: - name = request.node.name - self._name = name - self._path: Path = tmp_path_factory.mktemp(name, numbered=True) - #: A list of plugins to use with :py:meth:`parseconfig` and - #: :py:meth:`runpytest`. Initially this is an empty list but plugins can - #: be added to the list. - #: - #: When running in subprocess mode, specify plugins by name (str) - adding - #: plugin objects directly is not supported. - self.plugins: list[str | _PluggyPlugin] = [] - self._sys_path_snapshot = SysPathsSnapshot() - self._sys_modules_snapshot = self.__take_sys_modules_snapshot() - self._request.addfinalizer(self._finalize) - self._method = self._request.config.getoption("--runpytest") - self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) - - self._monkeypatch = mp = monkeypatch - self.chdir() - mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) - # Ensure no unexpected caching via tox. - mp.delenv("TOX_ENV_DIR", raising=False) - # Discard outer pytest options. - mp.delenv("PYTEST_ADDOPTS", raising=False) - # Ensure no user config is used. - tmphome = str(self.path) - mp.setenv("HOME", tmphome) - mp.setenv("USERPROFILE", tmphome) - # Do not use colors for inner runs by default. - mp.setenv("PY_COLORS", "0") - - @property - def path(self) -> Path: - """Temporary directory path used to create files/run tests from, etc.""" - return self._path - - def __repr__(self) -> str: - return f"" - - def _finalize(self) -> None: - """ - Clean up global state artifacts. - - Some methods modify the global interpreter state and this tries to - clean this up. It does not remove the temporary directory however so - it can be looked at after the test run has finished. - """ - self._sys_modules_snapshot.restore() - self._sys_path_snapshot.restore() - - def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: - # Some zope modules used by twisted-related tests keep internal state - # and can't be deleted; we had some trouble in the past with - # `zope.interface` for example. - # - # Preserve readline due to https://bugs.python.org/issue41033. - # pexpect issues a SIGWINCH. - def preserve_module(name): - return name.startswith(("zope", "readline")) - - return SysModulesSnapshot(preserve=preserve_module) - - def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: - """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" - pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) # type: ignore[attr-defined] - self._request.addfinalizer(reprec.finish_recording) - return reprec - - def chdir(self) -> None: - """Cd into the temporary directory. - - This is done automatically upon instantiation. - """ - self._monkeypatch.chdir(self.path) - - def _makefile( - self, - ext: str, - lines: Sequence[Any | bytes], - files: Mapping[str, _FileContent], - encoding: str = "utf-8", - ) -> Path: - items = list(files.items()) - - if ext is None: - raise TypeError("ext must not be None") - - if ext and not ext.startswith("."): - raise ValueError( - f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" - ) - - def to_text(s: Any | bytes) -> str: - return s.decode(encoding) if isinstance(s, bytes) else str(s) - - if lines: - source = "\n".join(to_text(x) for x in lines) - basename = self._name - items.insert(0, (basename, source)) - - ret = None - for basename, value in items: - p = self.path.joinpath(basename).with_suffix(ext) - p.parent.mkdir(parents=True, exist_ok=True) - source_ = Source(value) - source = "\n".join(to_text(line) for line in source_.lines) - p.write_text(source.strip(), encoding=encoding) - if ret is None: - ret = p - assert ret is not None - return ret - - def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: - r"""Create new text file(s) in the test directory. - - :param ext: - The extension the file(s) should use, including the dot, e.g. `.py`. - :param args: - All args are treated as strings and joined using newlines. - The result is written as contents to the file. The name of the - file is based on the test function requesting this fixture. - :param kwargs: - Each keyword is the name of a file, while the value of it will - be written as contents of the file. - :returns: - The first created file. - - Examples: - - .. code-block:: python - - pytester.makefile(".txt", "line1", "line2") - - pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n") - - To create binary files, use :meth:`pathlib.Path.write_bytes` directly: - - .. code-block:: python - - filename = pytester.path.joinpath("foo.bin") - filename.write_bytes(b"...") - """ - return self._makefile(ext, args, kwargs) - - def makeconftest(self, source: str) -> Path: - """Write a conftest.py file. - - :param source: The contents. - :returns: The conftest.py file. - """ - return self.makepyfile(conftest=source) - - def makeini(self, source: str) -> Path: - """Write a tox.ini file. - - :param source: The contents. - :returns: The tox.ini file. - """ - return self.makefile(".ini", tox=source) - - def maketoml(self, source: str) -> Path: - """Write a pytest.toml file. - - :param source: The contents. - :returns: The pytest.toml file. - - .. versionadded:: 9.0 - """ - return self.makefile(".toml", pytest=source) - - def getinicfg(self, source: str) -> SectionWrapper: - """Return the pytest section from the tox.ini config file.""" - p = self.makeini(source) - return IniConfig(str(p))["pytest"] - - def makepyprojecttoml(self, source: str) -> Path: - """Write a pyproject.toml file. - - :param source: The contents. - :returns: The pyproject.ini file. - - .. versionadded:: 6.0 - """ - return self.makefile(".toml", pyproject=source) - - def makepyfile(self, *args: _FileContent, **kwargs: _FileContent) -> Path: - r"""Shortcut for .makefile() with a .py extension. - - Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting - existing files. - - Examples: - - .. code-block:: python - - def test_something(pytester): - # Initial file is created test_something.py. - pytester.makepyfile("foobar") - # To create multiple files, pass kwargs accordingly. - pytester.makepyfile(custom="foobar") - # At this point, both 'test_something.py' & 'custom.py' exist in the test directory. - - """ - return self._makefile(".py", args, kwargs) - - def maketxtfile(self, *args: _FileContent, **kwargs: _FileContent) -> Path: - r"""Shortcut for .makefile() with a .txt extension. - - Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting - existing files. - - Examples: - - .. code-block:: python - - def test_something(pytester): - # Initial file is created test_something.txt. - pytester.maketxtfile("foobar") - # To create multiple files, pass kwargs accordingly. - pytester.maketxtfile(custom="foobar") - # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory. - - """ - return self._makefile(".txt", args, kwargs) - - def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: - """Prepend a directory to sys.path, defaults to :attr:`path`. - - This is undone automatically when this object dies at the end of each - test. - - :param path: - The path. - """ - if path is None: - path = self.path - - self._monkeypatch.syspath_prepend(str(path)) - - def mkdir(self, name: str | os.PathLike[str]) -> Path: - """Create a new (sub)directory. - - :param name: - The name of the directory, relative to the pytester path. - :returns: - The created directory. - :rtype: pathlib.Path - """ - p = self.path / name - p.mkdir() - return p - - def mkpydir(self, name: str | os.PathLike[str]) -> Path: - """Create a new python package. - - This creates a (sub)directory with an empty ``__init__.py`` file so it - gets recognised as a Python package. - """ - p = self.path / name - p.mkdir() - p.joinpath("__init__.py").touch() - return p - - def copy_example(self, name: str | None = None) -> Path: - """Copy file from project's directory into the testdir. - - :param name: - The name of the file to copy. - :return: - Path to the copied directory (inside ``self.path``). - :rtype: pathlib.Path - """ - example_dir_ = self._request.config.getini("pytester_example_dir") - if example_dir_ is None: - raise ValueError("pytester_example_dir is unset, can't copy examples") - example_dir: Path = self._request.config.rootpath / example_dir_ - - for extra_element in self._request.node.iter_markers("pytester_example_path"): - assert extra_element.args - example_dir = example_dir.joinpath(*extra_element.args) - - if name is None: - func_name = self._name - maybe_dir = example_dir / func_name - maybe_file = example_dir / (func_name + ".py") - - if maybe_dir.is_dir(): - example_path = maybe_dir - elif maybe_file.is_file(): - example_path = maybe_file - else: - raise LookupError( - f"{func_name} can't be found as module or package in {example_dir}" - ) - else: - example_path = example_dir.joinpath(name) - - if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): - shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True) - return self.path - elif example_path.is_file(): - result = self.path.joinpath(example_path.name) - shutil.copy(example_path, result) - return result - else: - raise LookupError( - f'example "{example_path}" is not found as a file or directory' - ) - - def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: - """Get the collection node of a file. - - :param config: - A pytest config. - See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. - :param arg: - Path to the file. - :returns: - The node. - """ - session = Session.from_config(config) - assert "::" not in str(arg) - p = Path(os.path.abspath(arg)) - config.hook.pytest_sessionstart(session=session) - res = session.perform_collect([str(p)], genitems=False)[0] - config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) - return res - - def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: - """Return the collection node of a file. - - This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to - create the (configured) pytest Config instance. - - :param path: - Path to the file. - :returns: - The node. - """ - path = Path(path) - config = self.parseconfigure(path) - session = Session.from_config(config) - x = bestrelpath(session.path, path) - config.hook.pytest_sessionstart(session=session) - res = session.perform_collect([x], genitems=False)[0] - config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) - return res - - def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: - """Generate all test items from a collection node. - - This recurses into the collection node and returns a list of all the - test items contained within. - - :param colitems: - The collection nodes. - :returns: - The collected items. - """ - session = colitems[0].session - result: list[Item] = [] - for colitem in colitems: - result.extend(session.genitems(colitem)) - return result - - def runitem(self, source: str) -> Any: - """Run the "test_func" Item. - - The calling test instance (class containing the test method) must - provide a ``.getrunner()`` method which should return a runner which - can run the test protocol for a single item, e.g. - ``_pytest.runner.runtestprotocol``. - """ - # used from runner functional tests - item = self.getitem(source) - # the test class where we are called from wants to provide the runner - testclassinstance = self._request.instance - runner = testclassinstance.getrunner() - return runner(item) - - def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: - """Run a test module in process using ``pytest.main()``. - - This run writes "source" into a temporary file and runs - ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance - for the result. - - :param source: The source code of the test module. - :param cmdlineargs: Any extra command line arguments to use. - """ - p = self.makepyfile(source) - values = [*list(cmdlineargs), p] - return self.inline_run(*values) - - def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: - """Run ``pytest.main(['--collect-only'])`` in-process. - - Runs the :py:func:`pytest.main` function to run all of pytest inside - the test process itself like :py:meth:`inline_run`, but returns a - tuple of the collected items and a :py:class:`HookRecorder` instance. - """ - rec = self.inline_run("--collect-only", *args) - items = [x.item for x in rec.getcalls("pytest_itemcollected")] - return items, rec - - def inline_run( - self, - *args: str | os.PathLike[str], - plugins=(), - no_reraise_ctrlc: bool = False, - ) -> HookRecorder: - """Run ``pytest.main()`` in-process, returning a HookRecorder. - - Runs the :py:func:`pytest.main` function to run all of pytest inside - the test process itself. This means it can return a - :py:class:`HookRecorder` instance which gives more detailed results - from that run than can be done by matching stdout/stderr from - :py:meth:`runpytest`. - - :param args: - Command line arguments to pass to :py:func:`pytest.main`. - :param plugins: - Extra plugin instances the ``pytest.main()`` instance should use. - :param no_reraise_ctrlc: - Typically we reraise keyboard interrupts from the child run. If - True, the KeyboardInterrupt exception is captured. - """ - from _pytest.unraisableexception import gc_collect_iterations_key - - # (maybe a cpython bug?) the importlib cache sometimes isn't updated - # properly between file creation and inline_run (especially if imports - # are interspersed with file creation) - importlib.invalidate_caches() - - plugins = list(plugins) - finalizers = [] - try: - # Any sys.module or sys.path changes done while running pytest - # inline should be reverted after the test run completes to avoid - # clashing with later inline tests run within the same pytest test, - # e.g. just because they use matching test module names. - finalizers.append(self.__take_sys_modules_snapshot().restore) - finalizers.append(SysPathsSnapshot().restore) - - # Important note: - # - our tests should not leave any other references/registrations - # laying around other than possibly loaded test modules - # referenced from sys.modules, as nothing will clean those up - # automatically - - rec = [] - - class PytesterHelperPlugin: - @staticmethod - def pytest_configure(config: Config) -> None: - rec.append(self.make_hook_recorder(config.pluginmanager)) - - # The unraisable plugin GC collect slows down inline - # pytester runs too much. - config.stash[gc_collect_iterations_key] = 0 - - plugins.append(PytesterHelperPlugin()) - ret = main([str(x) for x in args], plugins=plugins) - if len(rec) == 1: - reprec = rec.pop() - else: - - class reprec: # type: ignore - pass - - reprec.ret = ret - - # Typically we reraise keyboard interrupts from the child run - # because it's our user requesting interruption of the testing. - if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc: - calls = reprec.getcalls("pytest_keyboard_interrupt") - if calls and calls[-1].excinfo.type == KeyboardInterrupt: - raise KeyboardInterrupt() - return reprec - finally: - for finalizer in finalizers: - finalizer() - - def runpytest_inprocess( - self, *args: str | os.PathLike[str], **kwargs: Any - ) -> RunResult: - """Return result of running pytest in-process, providing a similar - interface to what self.runpytest() provides.""" - syspathinsert = kwargs.pop("syspathinsert", False) - - if syspathinsert: - self.syspathinsert() - instant = timing.Instant() - capture = _get_multicapture("sys") - capture.start_capturing() - try: - try: - reprec = self.inline_run(*args, **kwargs) - except SystemExit as e: - ret = e.args[0] - try: - ret = ExitCode(e.args[0]) - except ValueError: - pass - - class reprec: # type: ignore - ret = ret - - except Exception: - traceback.print_exc() - - class reprec: # type: ignore - ret = ExitCode(3) - - finally: - out, err = capture.readouterr() - capture.stop_capturing() - sys.stdout.write(out) - sys.stderr.write(err) - - assert reprec.ret is not None - res = RunResult( - reprec.ret, out.splitlines(), err.splitlines(), instant.elapsed().seconds - ) - res.reprec = reprec # type: ignore - return res - - def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: - """Run pytest inline or in a subprocess, depending on the command line - option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" - new_args = self._ensure_basetemp(args) - if self._method == "inprocess": - return self.runpytest_inprocess(*new_args, **kwargs) - elif self._method == "subprocess": - return self.runpytest_subprocess(*new_args, **kwargs) - raise RuntimeError(f"Unrecognized runpytest option: {self._method}") - - def _ensure_basetemp( - self, args: Sequence[str | os.PathLike[str]] - ) -> list[str | os.PathLike[str]]: - new_args = list(args) - for x in new_args: - if str(x).startswith("--basetemp"): - break - else: - new_args.append( - "--basetemp={}".format(self.path.parent.joinpath("basetemp")) - ) - return new_args - - def parseconfig(self, *args: str | os.PathLike[str]) -> Config: - """Return a new pytest :class:`pytest.Config` instance from given - commandline args. - - This invokes the pytest bootstrapping code in _pytest.config to create a - new :py:class:`pytest.PytestPluginManager` and call the - :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config` - instance. - - If :attr:`plugins` has been populated they should be plugin modules - to be registered with the plugin manager. - """ - import _pytest.config - - new_args = [str(x) for x in self._ensure_basetemp(args)] - - config = _pytest.config._prepareconfig(new_args, self.plugins) - # we don't know what the test will do with this half-setup config - # object and thus we make sure it gets unconfigured properly in any - # case (otherwise capturing could still be active, for example) - self._request.addfinalizer(config._ensure_unconfigure) - return config - - def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: - """Return a new pytest configured Config instance. - - Returns a new :py:class:`pytest.Config` instance like - :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure` - hook. - """ - config = self.parseconfig(*args) - config._do_configure() - return config - - def getitem( - self, source: str | os.PathLike[str], funcname: str = "test_func" - ) -> Item: - """Return the test item for a test function. - - Writes the source to a python file and runs pytest's collection on - the resulting module, returning the test item for the requested - function name. - - :param source: - The module source. - :param funcname: - The name of the test function for which to return a test item. - :returns: - The test item. - """ - items = self.getitems(source) - for item in items: - if item.name == funcname: - return item - assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" - - def getitems(self, source: str | os.PathLike[str]) -> list[Item]: - """Return all test items collected from the module. - - Writes the source to a Python file and runs pytest's collection on - the resulting module, returning all test items contained within. - """ - modcol = self.getmodulecol(source) - return self.genitems([modcol]) - - def getmodulecol( - self, - source: str | os.PathLike[str], - configargs=(), - *, - withinit: bool = False, - ): - """Return the module collection node for ``source``. - - Writes ``source`` to a file using :py:meth:`makepyfile` and then - runs the pytest collection on it, returning the collection node for the - test module. - - :param source: - The source code of the module to collect. - - :param configargs: - Any extra arguments to pass to :py:meth:`parseconfigure`. - - :param withinit: - Whether to also write an ``__init__.py`` file to the same - directory to ensure it is a package. - """ - if isinstance(source, os.PathLike): - path = self.path.joinpath(source) - assert not withinit, "not supported for paths" - else: - kw = {self._name: str(source)} - path = self.makepyfile(**kw) - if withinit: - self.makepyfile(__init__="#") - self.config = config = self.parseconfigure(path, *configargs) - return self.getnode(config, path) - - def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: - """Return the collection node for name from the module collection. - - Searches a module collection node for a collection node matching the - given name. - - :param modcol: A module collection node; see :py:meth:`getmodulecol`. - :param name: The name of the node to return. - """ - if modcol not in self._mod_collections: - self._mod_collections[modcol] = list(modcol.collect()) - for colitem in self._mod_collections[modcol]: - if colitem.name == name: - return colitem - return None - - def popen( - self, - cmdargs: Sequence[str | os.PathLike[str]], - stdout: int | TextIO = subprocess.PIPE, - stderr: int | TextIO = subprocess.PIPE, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, - **kw, - ): - """Invoke :py:class:`subprocess.Popen`. - - Calls :py:class:`subprocess.Popen` making sure the current working - directory is in ``PYTHONPATH``. - - You probably want to use :py:meth:`run` instead. - """ - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join( - filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) - ) - kw["env"] = env - - if stdin is self.CLOSE_STDIN: - kw["stdin"] = subprocess.PIPE - elif isinstance(stdin, bytes): - kw["stdin"] = subprocess.PIPE - else: - kw["stdin"] = stdin - - popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) - if stdin is self.CLOSE_STDIN: - assert popen.stdin is not None - popen.stdin.close() - elif isinstance(stdin, bytes): - assert popen.stdin is not None - popen.stdin.write(stdin) - - return popen - - def run( - self, - *cmdargs: str | os.PathLike[str], - timeout: float | None = None, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, - ) -> RunResult: - """Run a command with arguments. - - Run a process using :py:class:`subprocess.Popen` saving the stdout and - stderr. - - :param cmdargs: - The sequence of arguments to pass to :py:class:`subprocess.Popen`, - with path-like objects being converted to :py:class:`str` - automatically. - :param timeout: - The period in seconds after which to timeout and raise - :py:class:`Pytester.TimeoutExpired`. - :param stdin: - Optional standard input. - - - If it is ``CLOSE_STDIN`` (Default), then this method calls - :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and - the standard input is closed immediately after the new command is - started. - - - If it is of type :py:class:`bytes`, these bytes are sent to the - standard input of the command. - - - Otherwise, it is passed through to :py:class:`subprocess.Popen`. - For further information in this case, consult the document of the - ``stdin`` parameter in :py:class:`subprocess.Popen`. - :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int - :returns: - The result. - - """ - __tracebackhide__ = True - - cmdargs = tuple(os.fspath(arg) for arg in cmdargs) - p1 = self.path.joinpath("stdout") - p2 = self.path.joinpath("stderr") - print("running:", *cmdargs) - print(" in:", Path.cwd()) - - with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2: - instant = timing.Instant() - popen = self.popen( - cmdargs, - stdin=stdin, - stdout=f1, - stderr=f2, - ) - if popen.stdin is not None: - popen.stdin.close() - - def handle_timeout() -> None: - __tracebackhide__ = True - - timeout_message = f"{timeout} second timeout expired running: {cmdargs}" - - popen.kill() - popen.wait() - raise self.TimeoutExpired(timeout_message) - - if timeout is None: - ret = popen.wait() - else: - try: - ret = popen.wait(timeout) - except subprocess.TimeoutExpired: - handle_timeout() - f1.flush() - f2.flush() - - with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2: - out = f1.read().splitlines() - err = f2.read().splitlines() - - self._dump_lines(out, sys.stdout) - self._dump_lines(err, sys.stderr) - - with contextlib.suppress(ValueError): - ret = ExitCode(ret) - return RunResult(ret, out, err, instant.elapsed().seconds) - - def _dump_lines(self, lines, fp): - try: - for line in lines: - print(line, file=fp) - except UnicodeEncodeError: - print(f"couldn't print to {fp} because of encoding") - - def _getpytestargs(self) -> tuple[str, ...]: - return sys.executable, "-mpytest" - - def runpython(self, script: os.PathLike[str]) -> RunResult: - """Run a python script using sys.executable as interpreter.""" - return self.run(sys.executable, script) - - def runpython_c(self, command: str) -> RunResult: - """Run ``python -c "command"``.""" - return self.run(sys.executable, "-c", command) - - def runpytest_subprocess( - self, *args: str | os.PathLike[str], timeout: float | None = None - ) -> RunResult: - """Run pytest as a subprocess with given arguments. - - Any plugins added to the :py:attr:`plugins` list will be added using the - ``-p`` command line option. Additionally ``--basetemp`` is used to put - any temporary files and directories in a numbered directory prefixed - with "runpytest-" to not conflict with the normal numbered pytest - location for temporary files and directories. - - :param args: - The sequence of arguments to pass to the pytest subprocess. - :param timeout: - The period in seconds after which to timeout and raise - :py:class:`Pytester.TimeoutExpired`. - :returns: - The result. - """ - __tracebackhide__ = True - p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) - args = (f"--basetemp={p}", *args) - for plugin in self.plugins: - if not isinstance(plugin, str): - raise ValueError( - f"Specifying plugins as objects is not supported in pytester subprocess mode; " - f"specify by name instead: {plugin}" - ) - args = ("-p", plugin, *args) - args = self._getpytestargs() + args - return self.run(*args, timeout=timeout) - - def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: - """Run pytest using pexpect. - - This makes sure to use the right pytest and sets up the temporary - directory locations. - - The pexpect child is returned. - """ - basetemp = self.path / "temp-pexpect" - basetemp.mkdir(mode=0o700) - invoke = " ".join(map(str, self._getpytestargs())) - cmd = f"{invoke} --basetemp={basetemp} {string}" - return self.spawn(cmd, expect_timeout=expect_timeout) - - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: - """Run a command using pexpect. - - The pexpect child is returned. - """ - pexpect = importorskip("pexpect", "3.0") - if hasattr(sys, "pypy_version_info") and "64" in platform.machine(): - skip("pypy-64 bit not supported") - if not hasattr(pexpect, "spawn"): - skip("pexpect.spawn not available") - logfile = self.path.joinpath("spawn.out").open("wb") - - child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout) - self._request.addfinalizer(logfile.close) - return child - - -class LineComp: - def __init__(self) -> None: - self.stringio = StringIO() - """:class:`python:io.StringIO()` instance used for input.""" - - def assert_contains_lines(self, lines2: Sequence[str]) -> None: - """Assert that ``lines2`` are contained (linearly) in :attr:`stringio`'s value. - - Lines are matched using :func:`LineMatcher.fnmatch_lines `. - """ - __tracebackhide__ = True - val = self.stringio.getvalue() - self.stringio.truncate(0) - self.stringio.seek(0) - lines1 = val.split("\n") - LineMatcher(lines1).fnmatch_lines(lines2) - - -class LineMatcher: - """Flexible matching of text. - - This is a convenience class to test large texts like the output of - commands. - - The constructor takes a list of lines without their trailing newlines, i.e. - ``text.splitlines()``. - """ - - def __init__(self, lines: list[str]) -> None: - self.lines = lines - self._log_output: list[str] = [] - - def __str__(self) -> str: - """Return the entire original text. - - .. versionadded:: 6.2 - You can use :meth:`str` in older versions. - """ - return "\n".join(self.lines) - - def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: - if isinstance(lines2, str): - lines2 = Source(lines2) - if isinstance(lines2, Source): - lines2 = lines2.strip().lines - return lines2 - - def fnmatch_lines_random(self, lines2: Sequence[str]) -> None: - """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`).""" - __tracebackhide__ = True - self._match_lines_random(lines2, fnmatch) - - def re_match_lines_random(self, lines2: Sequence[str]) -> None: - """Check lines exist in the output in any order (using :func:`python:re.match`).""" - __tracebackhide__ = True - self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name))) - - def _match_lines_random( - self, lines2: Sequence[str], match_func: Callable[[str, str], bool] - ) -> None: - __tracebackhide__ = True - lines2 = self._getlines(lines2) - for line in lines2: - for x in self.lines: - if line == x or match_func(x, line): - self._log("matched: ", repr(line)) - break - else: - msg = f"line {line!r} not found in output" - self._log(msg) - self._fail(msg) - - def get_lines_after(self, fnline: str) -> Sequence[str]: - """Return all lines following the given line in the text. - - The given line can contain glob wildcards. - """ - for i, line in enumerate(self.lines): - if fnline == line or fnmatch(line, fnline): - return self.lines[i + 1 :] - raise ValueError(f"line {fnline!r} not found in output") - - def _log(self, *args) -> None: - self._log_output.append(" ".join(str(x) for x in args)) - - @property - def _log_text(self) -> str: - return "\n".join(self._log_output) - - def fnmatch_lines( - self, lines2: Sequence[str], *, consecutive: bool = False - ) -> None: - """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`). - - The argument is a list of lines which have to match and can use glob - wildcards. If they do not match a pytest.fail() is called. The - matches and non-matches are also shown as part of the error message. - - :param lines2: String patterns to match. - :param consecutive: Match lines consecutively? - """ - __tracebackhide__ = True - self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive) - - def re_match_lines( - self, lines2: Sequence[str], *, consecutive: bool = False - ) -> None: - """Check lines exist in the output (using :func:`python:re.match`). - - The argument is a list of lines which have to match using ``re.match``. - If they do not match a pytest.fail() is called. - - The matches and non-matches are also shown as part of the error message. - - :param lines2: string patterns to match. - :param consecutive: match lines consecutively? - """ - __tracebackhide__ = True - self._match_lines( - lines2, - lambda name, pat: bool(re.match(pat, name)), - "re.match", - consecutive=consecutive, - ) - - def _match_lines( - self, - lines2: Sequence[str], - match_func: Callable[[str, str], bool], - match_nickname: str, - *, - consecutive: bool = False, - ) -> None: - """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``. - - :param Sequence[str] lines2: - List of string patterns to match. The actual format depends on - ``match_func``. - :param match_func: - A callable ``match_func(line, pattern)`` where line is the - captured line from stdout/stderr and pattern is the matching - pattern. - :param str match_nickname: - The nickname for the match function that will be logged to stdout - when a match occurs. - :param consecutive: - Match lines consecutively? - """ - if not isinstance(lines2, collections.abc.Sequence): - raise TypeError(f"invalid type for lines2: {type(lines2).__name__}") - lines2 = self._getlines(lines2) - lines1 = self.lines[:] - extralines = [] - __tracebackhide__ = True - wnick = len(match_nickname) + 1 - started = False - for line in lines2: - nomatchprinted = False - while lines1: - nextline = lines1.pop(0) - if line == nextline: - self._log("exact match:", repr(line)) - started = True - break - elif match_func(nextline, line): - self._log(f"{match_nickname}:", repr(line)) - self._log( - "{:>{width}}".format("with:", width=wnick), repr(nextline) - ) - started = True - break - else: - if consecutive and started: - msg = f"no consecutive match: {line!r}" - self._log(msg) - self._log( - "{:>{width}}".format("with:", width=wnick), repr(nextline) - ) - self._fail(msg) - if not nomatchprinted: - self._log( - "{:>{width}}".format("nomatch:", width=wnick), repr(line) - ) - nomatchprinted = True - self._log("{:>{width}}".format("and:", width=wnick), repr(nextline)) - extralines.append(nextline) - else: - msg = f"remains unmatched: {line!r}" - self._log(msg) - self._fail(msg) - self._log_output = [] - - def no_fnmatch_line(self, pat: str) -> None: - """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``. - - :param str pat: The pattern to match lines. - """ - __tracebackhide__ = True - self._no_match_line(pat, fnmatch, "fnmatch") - - def no_re_match_line(self, pat: str) -> None: - """Ensure captured lines do not match the given pattern, using ``re.match``. - - :param str pat: The regular expression to match lines. - """ - __tracebackhide__ = True - self._no_match_line( - pat, lambda name, pat: bool(re.match(pat, name)), "re.match" - ) - - def _no_match_line( - self, pat: str, match_func: Callable[[str, str], bool], match_nickname: str - ) -> None: - """Underlying implementation of ``no_fnmatch_line`` and ``no_re_match_line``. - - :param str pat: - The pattern to match lines. - :param match_func: - A callable ``match_func(line, pattern)`` where line is the - captured line from stdout/stderr and pattern is the matching - pattern. - :param match_nickname: - The nickname for the match function that will be logged to stdout - when a match occurs. - """ - __tracebackhide__ = True - nomatch_printed = False - wnick = len(match_nickname) + 1 - for line in self.lines: - if match_func(line, pat): - msg = f"{match_nickname}: {pat!r}" - self._log(msg) - self._log("{:>{width}}".format("with:", width=wnick), repr(line)) - self._fail(msg) - else: - if not nomatch_printed: - self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat)) - nomatch_printed = True - self._log("{:>{width}}".format("and:", width=wnick), repr(line)) - self._log_output = [] - - def _fail(self, msg: str) -> None: - __tracebackhide__ = True - log_text = self._log_text - self._log_output = [] - fail(log_text) - - def str(self) -> str: - """Return the entire original text.""" - return str(self) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/pytester_assertions.py b/tests/venv2/lib/python3.11/site-packages/_pytest/pytester_assertions.py deleted file mode 100644 index b8d8a19..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/pytester_assertions.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Helper plugin for pytester; should not be loaded on its own.""" - -# This plugin contains assertions used by pytester. pytester cannot -# contain them itself, since it is imported by the `pytest` module, -# hence cannot be subject to assertion rewriting, which requires a -# module to not be already imported. -from __future__ import annotations - -from collections.abc import Sequence - -from _pytest.reports import CollectReport -from _pytest.reports import TestReport - - -def assertoutcome( - outcomes: tuple[ - Sequence[TestReport], - Sequence[CollectReport | TestReport], - Sequence[CollectReport | TestReport], - ], - passed: int = 0, - skipped: int = 0, - failed: int = 0, -) -> None: - __tracebackhide__ = True - - realpassed, realskipped, realfailed = outcomes - obtained = { - "failed": len(realfailed), - "passed": len(realpassed), - "skipped": len(realskipped), - } - expected = {"failed": failed, "passed": passed, "skipped": skipped} - assert obtained == expected, outcomes - - -def assert_outcomes( - outcomes: dict[str, int], - passed: int = 0, - skipped: int = 0, - failed: int = 0, - errors: int = 0, - xpassed: int = 0, - xfailed: int = 0, - warnings: int | None = None, - deselected: int | None = None, -) -> None: - """Assert that the specified outcomes appear with the respective - numbers (0 means it didn't occur) in the text output from a test run.""" - __tracebackhide__ = True - - obtained = { - "passed": outcomes.get("passed", 0), - "skipped": outcomes.get("skipped", 0), - "failed": outcomes.get("failed", 0), - "errors": outcomes.get("errors", 0), - "xpassed": outcomes.get("xpassed", 0), - "xfailed": outcomes.get("xfailed", 0), - } - expected = { - "passed": passed, - "skipped": skipped, - "failed": failed, - "errors": errors, - "xpassed": xpassed, - "xfailed": xfailed, - } - if warnings is not None: - obtained["warnings"] = outcomes.get("warnings", 0) - expected["warnings"] = warnings - if deselected is not None: - obtained["deselected"] = outcomes.get("deselected", 0) - expected["deselected"] = deselected - assert obtained == expected diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/python.py b/tests/venv2/lib/python3.11/site-packages/_pytest/python.py deleted file mode 100644 index 45ff185..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/python.py +++ /dev/null @@ -1,1759 +0,0 @@ -# mypy: allow-untyped-defs -"""Python test discovery, setup and run of test functions.""" - -from __future__ import annotations - -import abc -from collections import Counter -from collections import defaultdict -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence -import dataclasses -import enum -import fnmatch -from functools import partial -import inspect -import itertools -import os -from pathlib import Path -import re -import textwrap -import types -from typing import Any -from typing import cast -from typing import final -from typing import Literal -from typing import NoReturn -from typing import TYPE_CHECKING -import warnings - -import _pytest -from _pytest import fixtures -from _pytest import nodes -from _pytest._code import filter_traceback -from _pytest._code import getfslineno -from _pytest._code.code import ExceptionInfo -from _pytest._code.code import TerminalRepr -from _pytest._code.code import Traceback -from _pytest._io.saferepr import saferepr -from _pytest.compat import ascii_escaped -from _pytest.compat import get_default_arg_names -from _pytest.compat import get_real_func -from _pytest.compat import getimfunc -from _pytest.compat import is_async_function -from _pytest.compat import NOTSET -from _pytest.compat import safe_getattr -from _pytest.compat import safe_isclass -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import _resolve_args_directness -from _pytest.fixtures import FixtureDef -from _pytest.fixtures import FixtureRequest -from _pytest.fixtures import FixtureValue -from _pytest.fixtures import FuncFixtureInfo -from _pytest.fixtures import get_scope_node -from _pytest.main import Session -from _pytest.mark import ParameterSet -from _pytest.mark.structures import _HiddenParam -from _pytest.mark.structures import get_unpacked_marks -from _pytest.mark.structures import HIDDEN_PARAM -from _pytest.mark.structures import Mark -from _pytest.mark.structures import MarkDecorator -from _pytest.mark.structures import normalize_mark_list -from _pytest.outcomes import fail -from _pytest.outcomes import skip -from _pytest.pathlib import fnmatch_ex -from _pytest.pathlib import import_path -from _pytest.pathlib import ImportPathMismatchError -from _pytest.pathlib import scandir -from _pytest.scope import Scope -from _pytest.scope import ScopeName -from _pytest.stash import StashKey -from _pytest.warning_types import PytestCollectionWarning -from _pytest.warning_types import PytestReturnNotNoneWarning - - -if TYPE_CHECKING: - from typing_extensions import Self - - -def pytest_addoption(parser: Parser) -> None: - parser.addini( - "python_files", - type="args", - # NOTE: default is also used in AssertionRewritingHook. - default=["test_*.py", "*_test.py"], - help="Glob-style file patterns for Python test module discovery", - ) - parser.addini( - "python_classes", - type="args", - default=["Test"], - help="Prefixes or glob names for Python test class discovery", - ) - parser.addini( - "python_functions", - type="args", - default=["test"], - help="Prefixes or glob names for Python test function and method discovery", - ) - parser.addini( - "disable_test_id_escaping_and_forfeit_all_rights_to_community_support", - type="bool", - default=False, - help="Disable string escape non-ASCII characters, might cause unwanted " - "side effects(use at your own risk)", - ) - parser.addini( - "strict_parametrization_ids", - type="bool", - # None => fallback to `strict`. - default=None, - help="Emit an error if non-unique parameter set IDs are detected", - ) - - -def pytest_generate_tests(metafunc: Metafunc) -> None: - for marker in metafunc.definition.iter_markers(name="parametrize"): - metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) - - -def pytest_configure(config: Config) -> None: - config.addinivalue_line( - "markers", - "parametrize(argnames, argvalues): call a test function multiple " - "times passing in different arguments in turn. argvalues generally " - "needs to be a list of values if argnames specifies only one name " - "or a list of tuples of values if argnames specifies multiple names. " - "Example: @parametrize('arg1', [1,2]) would lead to two calls of the " - "decorated test function, one with arg1=1 and another with arg1=2." - "see https://docs.pytest.org/en/stable/how-to/parametrize.html for more info " - "and examples.", - ) - config.addinivalue_line( - "markers", - "usefixtures(fixturename1, fixturename2, ...): mark tests as needing " - "all of the specified fixtures. see " - "https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures ", - ) - - -def async_fail(nodeid: str) -> None: - msg = ( - "async def functions are not natively supported.\n" - "You need to install a suitable plugin for your async framework, for example:\n" - " - anyio\n" - " - pytest-asyncio\n" - " - pytest-tornasync\n" - " - pytest-trio\n" - " - pytest-twisted" - ) - fail(msg, pytrace=False) - - -@hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: - testfunction = pyfuncitem.obj - if is_async_function(testfunction): - async_fail(pyfuncitem.nodeid) - funcargs = pyfuncitem.funcargs - testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} - result = testfunction(**testargs) - if hasattr(result, "__await__") or hasattr(result, "__aiter__"): - async_fail(pyfuncitem.nodeid) - elif result is not None: - warnings.warn( - PytestReturnNotNoneWarning( - f"Test functions should return None, but {pyfuncitem.nodeid} returned {type(result)!r}.\n" - "Did you mean to use `assert` instead of `return`?\n" - "See https://docs.pytest.org/en/stable/how-to/assert.html#return-not-none for more information." - ) - ) - return True - - -def pytest_collect_directory( - path: Path, parent: nodes.Collector -) -> nodes.Collector | None: - pkginit = path / "__init__.py" - try: - has_pkginit = pkginit.is_file() - except PermissionError: - # See https://github.com/pytest-dev/pytest/issues/12120#issuecomment-2106349096. - return None - if has_pkginit: - return Package.from_parent(parent, path=path) - return None - - -def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None: - if file_path.suffix == ".py": - if not parent.session.isinitpath(file_path): - if not path_matches_patterns( - file_path, parent.config.getini("python_files") - ): - return None - ihook = parent.session.gethookproxy(file_path) - module: Module = ihook.pytest_pycollect_makemodule( - module_path=file_path, parent=parent - ) - return module - return None - - -def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: - """Return whether path matches any of the patterns in the list of globs given.""" - return any(fnmatch_ex(pattern, path) for pattern in patterns) - - -def pytest_pycollect_makemodule(module_path: Path, parent) -> Module: - return Module.from_parent(parent, path=module_path) - - -@hookimpl(trylast=True) -def pytest_pycollect_makeitem( - collector: Module | Class, name: str, obj: object -) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]: - assert isinstance(collector, Class | Module), type(collector) - # Nothing was collected elsewhere, let's do it here. - if safe_isclass(obj): - if collector.istestclass(obj, name): - return Class.from_parent(collector, name=name, obj=obj) - elif collector.istestfunction(obj, name): - # mock seems to store unbound methods (issue473), normalize it. - obj = getattr(obj, "__func__", obj) - # We need to try and unwrap the function if it's a functools.partial - # or a functools.wrapped. - # We mustn't if it's been wrapped with mock.patch (python 2 only). - if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))): - filename, lineno = getfslineno(obj) - warnings.warn_explicit( - message=PytestCollectionWarning( - f"cannot collect {name!r} because it is not a function." - ), - category=None, - filename=str(filename), - lineno=lineno + 1, - ) - elif getattr(obj, "__test__", True): - if inspect.isgeneratorfunction(obj): - fail( - f"'yield' keyword is allowed in fixtures, but not in tests ({name})", - pytrace=False, - ) - return list(collector._genfunctions(name, obj)) - return None - return None - - -class PyobjMixin(nodes.Node): - """this mix-in inherits from Node to carry over the typing information - - as its intended to always mix in before a node - its position in the mro is unaffected""" - - _ALLOW_MARKERS = True - - @property - def module(self): - """Python module object this node was collected from (can be None).""" - node = self.getparent(Module) - return node.obj if node is not None else None - - @property - def cls(self): - """Python class object this node was collected from (can be None).""" - node = self.getparent(Class) - return node.obj if node is not None else None - - @property - def instance(self): - """Python instance object the function is bound to. - - Returns None if not a test method, e.g. for a standalone test function, - a class or a module. - """ - # Overridden by Function. - return None - - @property - def obj(self): - """Underlying Python object.""" - obj = getattr(self, "_obj", None) - if obj is None: - self._obj = obj = self._getobj() - # XXX evil hack - # used to avoid Function marker duplication - if self._ALLOW_MARKERS: - self.own_markers.extend(get_unpacked_marks(self.obj)) - # This assumes that `obj` is called before there is a chance - # to add custom keys to `self.keywords`, so no fear of overriding. - self.keywords.update((mark.name, mark) for mark in self.own_markers) - return obj - - @obj.setter - def obj(self, value): - self._obj = value - - def _getobj(self): - """Get the underlying Python object. May be overwritten by subclasses.""" - # TODO: Improve the type of `parent` such that assert/ignore aren't needed. - assert self.parent is not None - obj = self.parent.obj # type: ignore[attr-defined] - return getattr(obj, self.name) - - def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: - """Return Python path relative to the containing module.""" - parts = [] - for node in self.iter_parents(): - name = node.name - if isinstance(node, Module): - name = os.path.splitext(name)[0] - if stopatmodule: - if includemodule: - parts.append(name) - break - parts.append(name) - parts.reverse() - return ".".join(parts) - - def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: - # XXX caching? - path, lineno = getfslineno(self.obj) - modpath = self.getmodpath() - return path, lineno, modpath - - -# As an optimization, these builtin attribute names are pre-ignored when -# iterating over an object during collection -- the pytest_pycollect_makeitem -# hook is not called for them. -# fmt: off -class _EmptyClass: pass # noqa: E701 -IGNORED_ATTRIBUTES = frozenset.union( - frozenset(), - # Module. - dir(types.ModuleType("empty_module")), - # Some extra module attributes the above doesn't catch. - {"__builtins__", "__file__", "__cached__"}, - # Class. - dir(_EmptyClass), - # Instance. - dir(_EmptyClass()), -) -del _EmptyClass -# fmt: on - - -class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): - def funcnamefilter(self, name: str) -> bool: - return self._matches_prefix_or_glob_option("python_functions", name) - - def isnosetest(self, obj: object) -> bool: - """Look for the __test__ attribute, which is applied by the - @nose.tools.istest decorator. - """ - # We explicitly check for "is True" here to not mistakenly treat - # classes with a custom __getattr__ returning something truthy (like a - # function) as test classes. - return safe_getattr(obj, "__test__", False) is True - - def classnamefilter(self, name: str) -> bool: - return self._matches_prefix_or_glob_option("python_classes", name) - - def istestfunction(self, obj: object, name: str) -> bool: - if self.funcnamefilter(name) or self.isnosetest(obj): - if isinstance(obj, staticmethod | classmethod): - # staticmethods and classmethods need to be unwrapped. - obj = safe_getattr(obj, "__func__", False) - return callable(obj) and fixtures.getfixturemarker(obj) is None - else: - return False - - def istestclass(self, obj: object, name: str) -> bool: - if not (self.classnamefilter(name) or self.isnosetest(obj)): - return False - if inspect.isabstract(obj): - return False - return True - - def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: - """Check if the given name matches the prefix or glob-pattern defined - in configuration.""" - for option in self.config.getini(option_name): - if name.startswith(option): - return True - # Check that name looks like a glob-string before calling fnmatch - # because this is called for every name in each collected module, - # and fnmatch is somewhat expensive to call. - elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch( - name, option - ): - return True - return False - - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - if not getattr(self.obj, "__test__", True): - return [] - - # Avoid random getattrs and peek in the __dict__ instead. - dicts = [getattr(self.obj, "__dict__", {})] - if isinstance(self.obj, type): - for basecls in self.obj.__mro__: - dicts.append(basecls.__dict__) - - # In each class, nodes should be definition ordered. - # __dict__ is definition ordered. - seen: set[str] = set() - dict_values: list[list[nodes.Item | nodes.Collector]] = [] - collect_imported_tests = self.session.config.getini("collect_imported_tests") - ihook = self.ihook - for dic in dicts: - values: list[nodes.Item | nodes.Collector] = [] - # Note: seems like the dict can change during iteration - - # be careful not to remove the list() without consideration. - for name, obj in list(dic.items()): - if name in IGNORED_ATTRIBUTES: - continue - if name in seen: - continue - seen.add(name) - - if not collect_imported_tests and isinstance(self, Module): - # Do not collect functions and classes from other modules. - if inspect.isfunction(obj) or inspect.isclass(obj): - if obj.__module__ != self._getobj().__name__: - continue - - res = ihook.pytest_pycollect_makeitem( - collector=self, name=name, obj=obj - ) - if res is None: - continue - elif isinstance(res, list): - values.extend(res) - else: - values.append(res) - dict_values.append(values) - - # Between classes in the class hierarchy, reverse-MRO order -- nodes - # inherited from base classes should come before subclasses. - result = [] - for values in reversed(dict_values): - result.extend(values) - return result - - def _genfunctions(self, name: str, funcobj) -> Iterator[Function]: - modulecol = self.getparent(Module) - assert modulecol is not None - module = modulecol.obj - clscol = self.getparent(Class) - cls = (clscol and clscol.obj) or None - - definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj) - fixtureinfo = definition._fixtureinfo - - # pytest_generate_tests impls call metafunc.parametrize() which fills - # metafunc._calls, the outcome of the hook. - metafunc = Metafunc( - definition=definition, - fixtureinfo=fixtureinfo, - config=self.config, - cls=cls, - module=module, - _ispytest=True, - ) - methods = [] - if hasattr(module, "pytest_generate_tests"): - methods.append(module.pytest_generate_tests) - if cls is not None and hasattr(cls, "pytest_generate_tests"): - methods.append(cls().pytest_generate_tests) - self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc)) - - if not metafunc._calls: - yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) - else: - metafunc._recompute_direct_params_indices() - # Direct parametrizations taking place in module/class-specific - # `metafunc.parametrize` calls may have shadowed some fixtures, so make sure - # we update what the function really needs a.k.a its fixture closure. Note that - # direct parametrizations using `@pytest.mark.parametrize` have already been considered - # into making the closure using `ignore_args` arg to `getfixtureclosure`. - fixtureinfo.prune_dependency_tree() - - for callspec in metafunc._calls: - subname = f"{name}[{callspec.id}]" if callspec._idlist else name - yield Function.from_parent( - self, - name=subname, - callspec=callspec, - fixtureinfo=fixtureinfo, - keywords={callspec.id: True}, - originalname=name, - ) - - -def importtestmodule( - path: Path, - config: Config, -): - # We assume we are only called once per module. - importmode = config.getoption("--import-mode") - try: - mod = import_path( - path, - mode=importmode, - root=config.rootpath, - consider_namespace_packages=config.getini("consider_namespace_packages"), - ) - except SyntaxError as e: - raise nodes.Collector.CollectError( - ExceptionInfo.from_current().getrepr(style="short") - ) from e - except ImportPathMismatchError as e: - raise nodes.Collector.CollectError( - "import file mismatch:\n" - "imported module {!r} has this __file__ attribute:\n" - " {}\n" - "which is not the same as the test file we want to collect:\n" - " {}\n" - "HINT: remove __pycache__ / .pyc files and/or use a " - "unique basename for your test file modules".format(*e.args) - ) from e - except ImportError as e: - exc_info = ExceptionInfo.from_current() - if config.get_verbosity() < 2: - exc_info.traceback = exc_info.traceback.filter(filter_traceback) - exc_repr = ( - exc_info.getrepr(style="short") - if exc_info.traceback - else exc_info.exconly() - ) - formatted_tb = str(exc_repr) - raise nodes.Collector.CollectError( - f"ImportError while importing test module '{path}'.\n" - "Hint: make sure your test modules/packages have valid Python names.\n" - "Traceback:\n" - f"{formatted_tb}" - ) from e - except skip.Exception as e: - if e.allow_module_level: - raise - raise nodes.Collector.CollectError( - "Using pytest.skip outside of a test will skip the entire module. " - "If that's your intention, pass `allow_module_level=True`. " - "If you want to skip a specific test or an entire class, " - "use the @pytest.mark.skip or @pytest.mark.skipif decorators." - ) from e - config.pluginmanager.consider_module(mod) - return mod - - -class Module(nodes.File, PyCollector): - """Collector for test classes and functions in a Python module.""" - - def _getobj(self): - return importtestmodule(self.path, self.config) - - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - self._register_setup_module_fixture() - self._register_setup_function_fixture() - self.session._fixturemanager.parsefactories(self) - return super().collect() - - def _register_setup_module_fixture(self) -> None: - """Register an autouse, module-scoped fixture for the collected module object - that invokes setUpModule/tearDownModule if either or both are available. - - Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with - other fixtures (#517). - """ - setup_module = _get_first_non_fixture_func( - self.obj, ("setUpModule", "setup_module") - ) - teardown_module = _get_first_non_fixture_func( - self.obj, ("tearDownModule", "teardown_module") - ) - - if setup_module is None and teardown_module is None: - return - - def xunit_setup_module_fixture(request) -> Generator[None]: - module = request.module - if setup_module is not None: - _call_with_optional_argument(setup_module, module) - yield - if teardown_module is not None: - _call_with_optional_argument(teardown_module, module) - - fixtures.register_fixture( - # Use a unique name to speed up lookup. - name=f"_xunit_setup_module_fixture_{self.obj.__name__}", - func=xunit_setup_module_fixture, - node=self, - scope="module", - autouse=True, - ) - - def _register_setup_function_fixture(self) -> None: - """Register an autouse, function-scoped fixture for the collected module object - that invokes setup_function/teardown_function if either or both are available. - - Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with - other fixtures (#517). - """ - setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",)) - teardown_function = _get_first_non_fixture_func( - self.obj, ("teardown_function",) - ) - if setup_function is None and teardown_function is None: - return - - def xunit_setup_function_fixture(request) -> Generator[None]: - if request.instance is not None: - # in this case we are bound to an instance, so we need to let - # setup_method handle this - yield - return - function = request.function - if setup_function is not None: - _call_with_optional_argument(setup_function, function) - yield - if teardown_function is not None: - _call_with_optional_argument(teardown_function, function) - - fixtures.register_fixture( - # Use a unique name to speed up lookup. - name=f"_xunit_setup_function_fixture_{self.obj.__name__}", - func=xunit_setup_function_fixture, - node=self, - scope="function", - autouse=True, - ) - - -class Package(nodes.Directory): - """Collector for files and directories in a Python packages -- directories - with an `__init__.py` file. - - .. note:: - - Directories without an `__init__.py` file are instead collected by - :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory` - collectors. - - .. versionchanged:: 8.0 - - Now inherits from :class:`~pytest.Directory`. - """ - - def __init__( - self, - fspath: None, - parent: nodes.Collector, - # NOTE: following args are unused: - config=None, - session=None, - nodeid=None, - path: Path | None = None, - ) -> None: - # NOTE: Could be just the following, but kept as-is for compat. - # super().__init__(self, fspath, parent=parent) - session = parent.session - super().__init__( - fspath=fspath, - path=path, - parent=parent, - config=config, - session=session, - nodeid=nodeid, - ) - - def setup(self) -> None: - init_mod = importtestmodule(self.path / "__init__.py", self.config) - - # Not using fixtures to call setup_module here because autouse fixtures - # from packages are not called automatically (#4085). - setup_module = _get_first_non_fixture_func( - init_mod, ("setUpModule", "setup_module") - ) - if setup_module is not None: - _call_with_optional_argument(setup_module, init_mod) - - teardown_module = _get_first_non_fixture_func( - init_mod, ("tearDownModule", "teardown_module") - ) - if teardown_module is not None: - func = partial(_call_with_optional_argument, teardown_module, init_mod) - self.addfinalizer(func) - - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - # Always collect __init__.py first. - def sort_key(entry: os.DirEntry[str]) -> object: - return (entry.name != "__init__.py", entry.name) - - config = self.config - col: nodes.Collector | None - cols: Sequence[nodes.Collector] - ihook = self.ihook - for direntry in scandir(self.path, sort_key): - if direntry.is_dir(): - path = Path(direntry.path) - if not self.session.isinitpath(path, with_parents=True): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - col = ihook.pytest_collect_directory(path=path, parent=self) - if col is not None: - yield col - - elif direntry.is_file(): - path = Path(direntry.path) - if not self.session.isinitpath(path): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - cols = ihook.pytest_collect_file(file_path=path, parent=self) - yield from cols - - -def _call_with_optional_argument(func, arg) -> None: - """Call the given function with the given argument if func accepts one argument, otherwise - calls func without arguments.""" - arg_count = func.__code__.co_argcount - if inspect.ismethod(func): - arg_count -= 1 - if arg_count: - func(arg) - else: - func() - - -def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None: - """Return the attribute from the given object to be used as a setup/teardown - xunit-style function, but only if not marked as a fixture to avoid calling it twice. - """ - for name in names: - meth: object | None = getattr(obj, name, None) - if meth is not None and fixtures.getfixturemarker(meth) is None: - return meth - return None - - -class Class(PyCollector): - """Collector for test methods (and nested classes) in a Python class.""" - - @classmethod - def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] - """The public constructor.""" - return super().from_parent(name=name, parent=parent, **kw) - - def newinstance(self): - return self.obj() - - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - if not safe_getattr(self.obj, "__test__", True): - return [] - if hasinit(self.obj): - assert self.parent is not None - self.warn( - PytestCollectionWarning( - f"cannot collect test class {self.obj.__name__!r} because it has a " - f"__init__ constructor (from: {self.parent.nodeid})" - ) - ) - return [] - elif hasnew(self.obj): - assert self.parent is not None - self.warn( - PytestCollectionWarning( - f"cannot collect test class {self.obj.__name__!r} because it has a " - f"__new__ constructor (from: {self.parent.nodeid})" - ) - ) - return [] - - self._register_setup_class_fixture() - self._register_setup_method_fixture() - - self.session._fixturemanager.parsefactories( - holder=self.newinstance(), node=self - ) - - return super().collect() - - def _register_setup_class_fixture(self) -> None: - """Register an autouse, class scoped fixture into the collected class object - that invokes setup_class/teardown_class if either or both are available. - - Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with - other fixtures (#517). - """ - setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) - teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) - if setup_class is None and teardown_class is None: - return - - def xunit_setup_class_fixture(request) -> Generator[None]: - cls = request.cls - if setup_class is not None: - func = getimfunc(setup_class) - _call_with_optional_argument(func, cls) - yield - if teardown_class is not None: - func = getimfunc(teardown_class) - _call_with_optional_argument(func, cls) - - fixtures.register_fixture( - # Use a unique name to speed up lookup. - name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", - func=xunit_setup_class_fixture, - node=self, - scope="class", - autouse=True, - ) - - def _register_setup_method_fixture(self) -> None: - """Register an autouse, function scoped fixture into the collected class object - that invokes setup_method/teardown_method if either or both are available. - - Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with - other fixtures (#517). - """ - setup_name = "setup_method" - setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) - teardown_name = "teardown_method" - teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) - if setup_method is None and teardown_method is None: - return - - def xunit_setup_method_fixture(request) -> Generator[None]: - instance = request.instance - method = request.function - if setup_method is not None: - func = getattr(instance, setup_name) - _call_with_optional_argument(func, method) - yield - if teardown_method is not None: - func = getattr(instance, teardown_name) - _call_with_optional_argument(func, method) - - fixtures.register_fixture( - # Use a unique name to speed up lookup. - name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", - func=xunit_setup_method_fixture, - node=self, - scope="function", - autouse=True, - ) - - -def hasinit(obj: object) -> bool: - init: object = getattr(obj, "__init__", None) - if init: - return init != object.__init__ - return False - - -def hasnew(obj: object) -> bool: - new: object = getattr(obj, "__new__", None) - if new: - return new != object.__new__ - return False - - -@final -@dataclasses.dataclass(frozen=True) -class IdMaker: - """Make IDs for a parametrization.""" - - __slots__ = ( - "argnames", - "config", - "idfn", - "ids", - "nodeid", - "parametersets", - ) - - # The argnames of the parametrization. - argnames: Sequence[str] - # The ParameterSets of the parametrization. - parametersets: Sequence[ParameterSet] - # Optionally, a user-provided callable to make IDs for parameters in a - # ParameterSet. - idfn: Callable[[Any], object | None] | None - # Optionally, explicit IDs for ParameterSets by index. - ids: Sequence[object | None] | None - # Optionally, the pytest config. - # Used for controlling ASCII escaping, determining parametrization ID - # strictness, and for calling the :hook:`pytest_make_parametrize_id` hook. - config: Config | None - # Optionally, the ID of the node being parametrized. - # Used only for clearer error messages. - nodeid: str | None - - def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]: - """Make a unique identifier for each ParameterSet, that may be used to - identify the parametrization in a node ID. - - If strict_parametrization_ids is enabled, and duplicates are detected, - raises CollectError. Otherwise makes the IDs unique as follows: - - Format is -...-[counter], where prm_x_token is - - user-provided id, if given - - else an id derived from the value, applicable for certain types - - else - The counter suffix is appended only in case a string wouldn't be unique - otherwise. - """ - resolved_ids = list(self._resolve_ids()) - # All IDs must be unique! - if len(resolved_ids) != len(set(resolved_ids)): - # Record the number of occurrences of each ID. - id_counts = Counter(resolved_ids) - - if self._strict_parametrization_ids_enabled(): - parameters = ", ".join(self.argnames) - parametersets = ", ".join( - [saferepr(list(param.values)) for param in self.parametersets] - ) - ids = ", ".join( - id if id is not HIDDEN_PARAM else "" for id in resolved_ids - ) - duplicates = ", ".join( - id if id is not HIDDEN_PARAM else "" - for id, count in id_counts.items() - if count > 1 - ) - msg = textwrap.dedent(f""" - Duplicate parametrization IDs detected, but strict_parametrization_ids is set. - - Test name: {self.nodeid} - Parameters: {parameters} - Parameter sets: {parametersets} - IDs: {ids} - Duplicates: {duplicates} - - You can fix this problem using `@pytest.mark.parametrize(..., ids=...)` or `pytest.param(..., id=...)`. - """).strip() # noqa: E501 - raise nodes.Collector.CollectError(msg) - - # Map the ID to its next suffix. - id_suffixes: dict[str, int] = defaultdict(int) - # Suffix non-unique IDs to make them unique. - for index, id in enumerate(resolved_ids): - if id_counts[id] > 1: - if id is HIDDEN_PARAM: - self._complain_multiple_hidden_parameter_sets() - suffix = "" - if id and id[-1].isdigit(): - suffix = "_" - new_id = f"{id}{suffix}{id_suffixes[id]}" - while new_id in set(resolved_ids): - id_suffixes[id] += 1 - new_id = f"{id}{suffix}{id_suffixes[id]}" - resolved_ids[index] = new_id - id_suffixes[id] += 1 - assert len(resolved_ids) == len(set(resolved_ids)), ( - f"Internal error: {resolved_ids=}" - ) - return resolved_ids - - def _strict_parametrization_ids_enabled(self) -> bool: - if self.config is None: - return False - strict_parametrization_ids = self.config.getini("strict_parametrization_ids") - if strict_parametrization_ids is None: - strict_parametrization_ids = self.config.getini("strict") - return cast(bool, strict_parametrization_ids) - - def _resolve_ids(self) -> Iterable[str | _HiddenParam]: - """Resolve IDs for all ParameterSets (may contain duplicates).""" - for idx, parameterset in enumerate(self.parametersets): - if parameterset.id is not None: - # ID provided directly - pytest.param(..., id="...") - if parameterset.id is HIDDEN_PARAM: - yield HIDDEN_PARAM - else: - yield _ascii_escaped_by_config(parameterset.id, self.config) - elif self.ids and idx < len(self.ids) and self.ids[idx] is not None: - # ID provided in the IDs list - parametrize(..., ids=[...]). - if self.ids[idx] is HIDDEN_PARAM: - yield HIDDEN_PARAM - else: - yield self._idval_from_value_required(self.ids[idx], idx) - else: - # ID not provided - generate it. - yield "-".join( - self._idval(val, argname, idx) - for val, argname in zip( - parameterset.values, self.argnames, strict=True - ) - ) - - def _idval(self, val: object, argname: str, idx: int) -> str: - """Make an ID for a parameter in a ParameterSet.""" - idval = self._idval_from_function(val, argname, idx) - if idval is not None: - return idval - idval = self._idval_from_hook(val, argname) - if idval is not None: - return idval - idval = self._idval_from_value(val) - if idval is not None: - return idval - return self._idval_from_argname(argname, idx) - - def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None: - """Try to make an ID for a parameter in a ParameterSet using the - user-provided id callable, if given.""" - if self.idfn is None: - return None - try: - id = self.idfn(val) - except Exception as e: - prefix = f"{self.nodeid}: " if self.nodeid is not None else "" - msg = "error raised while trying to determine id of parameter '{}' at position {}" - msg = prefix + msg.format(argname, idx) - raise ValueError(msg) from e - if id is None: - return None - return self._idval_from_value(id) - - def _idval_from_hook(self, val: object, argname: str) -> str | None: - """Try to make an ID for a parameter in a ParameterSet by calling the - :hook:`pytest_make_parametrize_id` hook.""" - if self.config: - id: str | None = self.config.hook.pytest_make_parametrize_id( - config=self.config, val=val, argname=argname - ) - return id - return None - - def _idval_from_value(self, val: object) -> str | None: - """Try to make an ID for a parameter in a ParameterSet from its value, - if the value type is supported.""" - if isinstance(val, str | bytes): - return _ascii_escaped_by_config(val, self.config) - elif val is None or isinstance(val, float | int | bool | complex): - return str(val) - elif isinstance(val, re.Pattern): - return ascii_escaped(val.pattern) - elif val is NOTSET: - # Fallback to default. Note that NOTSET is an enum.Enum. - pass - elif isinstance(val, enum.Enum): - return str(val) - elif isinstance(getattr(val, "__name__", None), str): - # Name of a class, function, module, etc. - name: str = getattr(val, "__name__") - return name - return None - - def _idval_from_value_required(self, val: object, idx: int) -> str: - """Like _idval_from_value(), but fails if the type is not supported.""" - id = self._idval_from_value(val) - if id is not None: - return id - - # Fail. - prefix = self._make_error_prefix() - msg = ( - f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. " - "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." - ) - fail(msg, pytrace=False) - - @staticmethod - def _idval_from_argname(argname: str, idx: int) -> str: - """Make an ID for a parameter in a ParameterSet from the argument name - and the index of the ParameterSet.""" - return str(argname) + str(idx) - - def _complain_multiple_hidden_parameter_sets(self) -> NoReturn: - fail( - f"{self._make_error_prefix()}multiple instances of HIDDEN_PARAM " - "cannot be used in the same parametrize call, " - "because the tests names need to be unique." - ) - - def _make_error_prefix(self) -> str: - if self.nodeid is not None: - return f"In {self.nodeid}: " - else: - return "" - - -@final -@dataclasses.dataclass(frozen=True) -class CallSpec2: - """A planned parameterized invocation of a test function. - - Calculated during collection for a given test function's Metafunc. - Once collection is over, each callspec is turned into a single Item - and stored in item.callspec. - """ - - # arg name -> arg value which will be passed to a fixture of the same name. - params: dict[str, object] = dataclasses.field(default_factory=dict) - # arg name -> arg index. - indices: dict[str, int] = dataclasses.field(default_factory=dict) - # arg name -> parameter scope. - # Used for sorting parametrized resources. - _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) - # Parts which will be added to the item's name in `[..]` separated by "-". - _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) - # Marks which will be applied to the item. - marks: list[Mark] = dataclasses.field(default_factory=list) - - def setmulti( - self, - *, - argnames: Iterable[str], - valset: Iterable[object], - id: str | _HiddenParam, - marks: Iterable[Mark | MarkDecorator], - scope: Scope, - param_index: int, - nodeid: str, - ) -> CallSpec2: - params = self.params.copy() - indices = self.indices.copy() - arg2scope = dict(self._arg2scope) - for arg, val in zip(argnames, valset, strict=True): - if arg in params: - raise nodes.Collector.CollectError( - f"{nodeid}: duplicate parametrization of {arg!r}" - ) - params[arg] = val - indices[arg] = param_index - arg2scope[arg] = scope - return CallSpec2( - params=params, - indices=indices, - _arg2scope=arg2scope, - _idlist=self._idlist if id is HIDDEN_PARAM else [*self._idlist, id], - marks=[*self.marks, *normalize_mark_list(marks)], - ) - - def getparam(self, name: str) -> object: - try: - return self.params[name] - except KeyError as e: - raise ValueError(name) from e - - @property - def id(self) -> str: - return "-".join(self._idlist) - - -def get_direct_param_fixture_func(request: FixtureRequest) -> Any: - return request.param - - -class DirectParamFixtureDef(FixtureDef[FixtureValue]): - """A custom FixtureDef for direct parametrization fixtures. - - Each parameter in direct parametrization is desugared to a parametrized - fixture which returns the direct parameterization value as its param. - We use this custom type as a "marker" for this type of FixtureDef, but - usually behaves like any other FixtureDef. - """ - - def __init__(self, *, node: nodes.Node, argname: str, scope: Scope) -> None: - super().__init__( - config=node.config, - baseid=NOTSET, - argname=argname, - func=get_direct_param_fixture_func, - scope=scope, - params=None, - ids=None, - node=node, - _ispytest=True, - ) - - -# Used for storing fixturedefs for direct parametrization. -name2directparamfixturedef_key = StashKey[dict[str, DirectParamFixtureDef[object]]]() - - -@final -class Metafunc: - """Objects passed to the :hook:`pytest_generate_tests` hook. - - They help to inspect a test function and to generate tests according to - test configuration or values specified in the class or module where a - test function is defined. - """ - - def __init__( - self, - definition: FunctionDefinition, - fixtureinfo: fixtures.FuncFixtureInfo, - config: Config, - cls=None, - module=None, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - - #: Access to the underlying :class:`_pytest.python.FunctionDefinition`. - self.definition = definition - - #: Access to the :class:`pytest.Config` object for the test session. - self.config = config - - #: The module object where the test function is defined in. - self.module = module - - #: Underlying Python test function. - self.function = definition.obj - - #: Set of fixture names required by the test function. - self.fixturenames = fixtureinfo.names_closure - - #: Class object where the test function is defined in or ``None``. - self.cls = cls - - self._arg2fixturedefs = fixtureinfo.name2fixturedefs - - # Result of parametrize(). - self._calls: list[CallSpec2] = [] - - self._params_directness: dict[str, Literal["indirect", "direct"]] = {} - - def parametrize( - self, - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], - indirect: bool | Sequence[str] = False, - ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, - scope: ScopeName | None = None, - *, - _param_mark: Mark | None = None, - ) -> None: - """Add new invocations to the underlying test function using the list - of argvalues for the given argnames. Parametrization is performed - during the collection phase. If you need to setup expensive resources - see about setting ``indirect`` to do it at test setup time instead. - - Can be called multiple times per test function (but only on different - argument names), in which case each call parametrizes all previous - parametrizations, e.g. - - :: - - unparametrized: t - parametrize ["x", "y"]: t[x], t[y] - parametrize [1, 2]: t[x-1], t[x-2], t[y-1], t[y-2] - - :param argnames: - A comma-separated string denoting one or more argument names, or - a list/tuple of argument strings. - - :param argvalues: - The list of argvalues determines how often a test is invoked with - different argument values. - - If only one argname was specified argvalues is a list of values. - If N argnames were specified, argvalues must be a list of - N-tuples, where each tuple-element specifies a value for its - respective argname. - - .. versionchanged:: 9.1 - - Passing a non-:class:`~collections.abc.Collection` iterable - (such as a generator or iterator) is deprecated. See - :ref:`parametrize-iterators` for details. - - :param indirect: - A list of arguments' names (subset of argnames) or a boolean. - If True the list contains all names from the argnames. Each - argvalue corresponding to an argname in this list will - be passed as request.param to its respective argname fixture - function so that it can perform more expensive setups during the - setup phase of a test rather than at collection time. - - :param ids: - Sequence of (or generator for) ids for ``argvalues``, - or a callable to return part of the id for each argvalue. - - With sequences (and generators like ``itertools.count()``) the - returned ids should be of type ``string``, ``int``, ``float``, - ``bool``, or ``None``. - They are mapped to the corresponding index in ``argvalues``. - ``None`` means to use the auto-generated id. - - .. versionadded:: 8.4 - :ref:`hidden-param` means to hide the parameter set - from the test name. Can only be used at most 1 time, as - test names need to be unique. - - If it is a callable it will be called for each entry in - ``argvalues``, and the return value is used as part of the - auto-generated id for the whole set (where parts are joined with - dashes ("-")). - This is useful to provide more specific ids for certain items, e.g. - dates. Returning ``None`` will use an auto-generated id. - - If no ids are provided they will be generated automatically from - the argvalues. - - :param scope: - If specified it denotes the scope of the parameters. - The scope is used for grouping tests by parameter instances. - It will also override any fixture-function defined scope, allowing - to set a dynamic scope using test context or configuration. - """ - nodeid = self.definition.nodeid - - argnames, parametersets = ParameterSet._for_parametrize( - argnames, - argvalues, - self.function, - self.config, - nodeid=self.definition.nodeid, - ) - del argvalues - - if "request" in argnames: - fail( - f"{nodeid}: 'request' is a reserved name and cannot be used in @pytest.mark.parametrize", - pytrace=False, - ) - - if scope is not None: - scope_ = Scope.from_user( - scope, descr=f"parametrize() call in {self.function.__name__}" - ) - else: - scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) - - self._validate_if_using_arg_names(argnames, indirect) - - # Use any already (possibly) generated ids with parametrize Marks. - if _param_mark and _param_mark._param_ids_from: - generated_ids = _param_mark._param_ids_from._param_ids_generated - if generated_ids is not None: - ids = generated_ids - - ids = self._resolve_parameter_set_ids( - argnames, ids, parametersets, nodeid=self.definition.nodeid - ) - - # Store used (possibly generated) ids with parametrize Marks. - if _param_mark and _param_mark._param_ids_from and generated_ids is None: - object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids) - - # Calculate directness. - arg_directness = _resolve_args_directness( - argnames, indirect, self.definition.nodeid - ) - self._params_directness.update(arg_directness) - - # Add direct parametrizations as fixturedefs to arg2fixturedefs by - # registering artificial DirectParamFixtureDef's such that later at test - # setup time we can rely on FixtureDefs to exist for all argnames. - node = None - # For scopes higher than function, a DirectParamFixtureDef might have - # already been created for the scope. We thus store and cache the - # DirectParamFixtureDef on the node related to the scope. - if scope_ is Scope.Function: - name2directparamfixturedef = None - else: - collector = self.definition.parent - assert collector is not None - node = get_scope_node(collector, scope_) - if node is None: - # If used class scope and there is no class, use module-level - # collector (for now). - if scope_ is Scope.Class: - assert isinstance(collector, Module) - node = collector - # If used package scope and there is no package, use session - # (for now). - elif scope_ is Scope.Package: - node = collector.session - else: - assert False, f"Unhandled missing scope: {scope}" - default: dict[str, DirectParamFixtureDef[object]] = {} - name2directparamfixturedef = node.stash.setdefault( - name2directparamfixturedef_key, default - ) - for argname in argnames: - if arg_directness[argname] == "indirect": - continue - if ( - name2directparamfixturedef is not None - and argname in name2directparamfixturedef - ): - fixturedef = name2directparamfixturedef[argname] - else: - fixturedef = DirectParamFixtureDef( - node=self.definition.session, - argname=argname, - scope=scope_, - ) - if name2directparamfixturedef is not None: - name2directparamfixturedef[argname] = fixturedef - self._arg2fixturedefs[argname] = [fixturedef] - - # Create the new calls: if we are parametrize() multiple times (by applying the decorator - # more than once) then we accumulate those calls generating the cartesian product - # of all calls. - newcalls = [] - for callspec in self._calls or [CallSpec2()]: - for param_index, (param_id, param_set) in enumerate( - zip(ids, parametersets, strict=True) - ): - newcallspec = callspec.setmulti( - argnames=argnames, - valset=param_set.values, - id=param_id, - marks=param_set.marks, - scope=scope_, - param_index=param_index, - nodeid=nodeid, - ) - newcalls.append(newcallspec) - self._calls = newcalls - - def _resolve_parameter_set_ids( - self, - argnames: Sequence[str], - ids: Iterable[object | None] | Callable[[Any], object | None] | None, - parametersets: Sequence[ParameterSet], - nodeid: str, - ) -> list[str | _HiddenParam]: - """Resolve the actual ids for the given parameter sets. - - :param argnames: - Argument names passed to ``parametrize()``. - :param ids: - The `ids` parameter of the ``parametrize()`` call (see docs). - :param parametersets: - The parameter sets, each containing a set of values corresponding - to ``argnames``. - :param nodeid str: - The nodeid of the definition item that generated this - parametrization. - :returns: - List with ids for each parameter set given. - """ - if ids is None: - idfn = None - ids_ = None - elif callable(ids): - idfn = ids - ids_ = None - else: - idfn = None - ids_ = self._validate_ids(ids, parametersets) - id_maker = IdMaker( - argnames, - parametersets, - idfn, - ids_, - self.config, - nodeid=nodeid, - ) - return id_maker.make_unique_parameterset_ids() - - def _validate_ids( - self, - ids: Iterable[object | None], - parametersets: Sequence[ParameterSet], - ) -> list[object | None]: - try: - num_ids = len(ids) # type: ignore[arg-type] - except TypeError: - try: - iter(ids) - except TypeError as e: - raise TypeError("ids must be a callable or an iterable") from e - num_ids = len(parametersets) - - # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 - if num_ids != len(parametersets) and num_ids != 0: - nodeid = self.definition.nodeid - fail( - f"In {nodeid}: {len(parametersets)} parameter sets specified, with different number of ids: {num_ids}", - pytrace=False, - ) - - return list(itertools.islice(ids, num_ids)) - - def _validate_if_using_arg_names( - self, - argnames: Sequence[str], - indirect: bool | Sequence[str], - ) -> None: - """Check if all argnames are being used, by default values, or directly/indirectly. - - :param List[str] argnames: List of argument names passed to ``parametrize()``. - :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. - :raises ValueError: If validation fails. - """ - default_arg_names = set(get_default_arg_names(self.function)) - nodeid = self.definition.nodeid - for arg in argnames: - if arg not in self.fixturenames: - if arg in default_arg_names: - fail( - f"In {nodeid}: function already takes an argument '{arg}' with a default value", - pytrace=False, - ) - else: - if isinstance(indirect, Sequence): - name = "fixture" if arg in indirect else "argument" - else: - name = "fixture" if indirect else "argument" - fail( - f"In {nodeid}: function uses no {name} '{arg}'", - pytrace=False, - ) - - def _recompute_direct_params_indices(self) -> None: - for argname, param_type in self._params_directness.items(): - if param_type == "direct": - for i, callspec in enumerate(self._calls): - callspec.indices[argname] = i - - -def _find_parametrized_scope( - argnames: Sequence[str], - arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], - indirect: bool | Sequence[str], -) -> Scope: - """Find the most appropriate scope for a parametrized call based on its arguments. - - When there's at least one direct argument, always use "function" scope. - - When a test function is parametrized and all its arguments are indirect - (e.g. fixtures), return the most narrow scope based on the fixtures used. - - Related to issue #1832, based on code posted by @Kingdread. - """ - if isinstance(indirect, Sequence): - all_arguments_are_fixtures = len(indirect) == len(argnames) - else: - all_arguments_are_fixtures = bool(indirect) - - if all_arguments_are_fixtures: - fixturedefs = arg2fixturedefs or {} - used_scopes = [ - fixturedef[-1]._scope - for name, fixturedef in fixturedefs.items() - if name in argnames - ] - # Takes the most narrow scope from used fixtures. - return min(used_scopes, default=Scope.Function) - - return Scope.Function - - -def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: - if config is None: - escape_option = False - else: - escape_option = config.getini( - "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - ) - # TODO: If escaping is turned off and the user passes bytes, - # will return a bytes. For now we ignore this but the - # code *probably* doesn't handle this case. - return val if escape_option else ascii_escaped(val) # type: ignore - - -class Function(PyobjMixin, nodes.Item): - """Item responsible for setting up and executing a Python test function. - - :param name: - The full function name, including any decorations like those - added by parametrization (``my_func[my_param]``). - :param parent: - The parent Node. - :param config: - The pytest Config object. - :param callspec: - If given, this function has been parametrized and the callspec contains - meta information about the parametrization. - :param callobj: - If given, the object which will be called when the Function is invoked, - otherwise the callobj will be obtained from ``parent`` using ``originalname``. - :param keywords: - Keywords bound to the function object for "-k" matching. - :param session: - The pytest Session object. - :param fixtureinfo: - Fixture information already resolved at this fixture node.. - :param originalname: - The attribute name to use for accessing the underlying function object. - Defaults to ``name``. Set this if name is different from the original name, - for example when it contains decorations like those added by parametrization - (``my_func[my_param]``). - """ - - # Disable since functions handle it themselves. - _ALLOW_MARKERS = False - - def __init__( - self, - name: str, - parent, - config: Config | None = None, - callspec: CallSpec2 | None = None, - callobj=NOTSET, - keywords: Mapping[str, Any] | None = None, - session: Session | None = None, - fixtureinfo: FuncFixtureInfo | None = None, - originalname: str | None = None, - ) -> None: - super().__init__(name, parent, config=config, session=session) - - if callobj is not NOTSET: - self._obj = callobj - self._instance = getattr(callobj, "__self__", None) - - #: Original function name, without any decorations (for example - #: parametrization adds a ``"[...]"`` suffix to function names), used to access - #: the underlying function object from ``parent`` (in case ``callobj`` is not given - #: explicitly). - #: - #: .. versionadded:: 3.0 - self.originalname = originalname or name - - # Note: when FunctionDefinition is introduced, we should change ``originalname`` - # to a readonly property that returns FunctionDefinition.name. - - self.own_markers.extend(get_unpacked_marks(self.obj)) - if callspec: - self.callspec = callspec - self.own_markers.extend(callspec.marks) - - # todo: this is a hell of a hack - # https://github.com/pytest-dev/pytest/issues/4569 - # Note: the order of the updates is important here; indicates what - # takes priority (ctor argument over function attributes over markers). - # Take own_markers only; NodeKeywords handles parent traversal on its own. - self.keywords.update((mark.name, mark) for mark in self.own_markers) - self.keywords.update(self.obj.__dict__) - if keywords: - self.keywords.update(keywords) - - if fixtureinfo is None: - fm = self.session._fixturemanager - fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) - self._fixtureinfo: FuncFixtureInfo = fixtureinfo - self.fixturenames = fixtureinfo.names_closure - self._initrequest() - - # todo: determine sound type limitations - @classmethod - def from_parent(cls, parent, **kw) -> Self: - """The public constructor.""" - return super().from_parent(parent=parent, **kw) - - def _initrequest(self) -> None: - self.funcargs: dict[str, object] = {} - self._request = fixtures.TopRequest(self, _ispytest=True) - - @property - def function(self): - """Underlying python 'function' object.""" - return getimfunc(self.obj) - - @property - def instance(self): - try: - return self._instance - except AttributeError: - if isinstance(self.parent, Class): - # Each Function gets a fresh class instance. - self._instance = self._getinstance() - else: - self._instance = None - return self._instance - - def _getinstance(self): - if isinstance(self.parent, Class): - # Each Function gets a fresh class instance. - return self.parent.newinstance() - else: - return None - - def _getobj(self): - instance = self.instance - if instance is not None: - parent_obj = instance - else: - assert self.parent is not None - parent_obj = self.parent.obj # type: ignore[attr-defined] - return getattr(parent_obj, self.originalname) - - @property - def _pyfuncitem(self): - """(compatonly) for code expecting pytest-2.2 style request objects.""" - return self - - def runtest(self) -> None: - """Execute the underlying test function.""" - self.ihook.pytest_pyfunc_call(pyfuncitem=self) - - def setup(self) -> None: - self._request._fillfixtures() - - def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: - if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): - code = _pytest._code.Code.from_function(get_real_func(self.obj)) - path, firstlineno = code.path, code.firstlineno - traceback = excinfo.traceback - ntraceback = traceback.cut(path=path, firstlineno=firstlineno) - if ntraceback == traceback: - ntraceback = ntraceback.cut(path=path) - if ntraceback == traceback: - ntraceback = ntraceback.filter(filter_traceback) - if not ntraceback: - ntraceback = traceback - ntraceback = ntraceback.filter(excinfo) - - # issue364: mark all but first and last frames to - # only show a single-line message for each frame. - if self.config.getoption("tbstyle", "auto") == "auto": - if len(ntraceback) > 2: - ntraceback = Traceback( - ( - ntraceback[0], - *(t.with_repr_style("short") for t in ntraceback[1:-1]), - ntraceback[-1], - ) - ) - - return ntraceback - return excinfo.traceback - - # TODO: Type ignored -- breaks Liskov Substitution. - def repr_failure( # type: ignore[override] - self, - excinfo: ExceptionInfo[BaseException], - ) -> str | TerminalRepr: - style = self.config.getoption("tbstyle", "auto") - if style == "auto": - style = "long" - return self._repr_failure_py(excinfo, style=style) - - -class FunctionDefinition(Function): - """This class is a stop gap solution until we evolve to have actual function - definition nodes and manage to get rid of ``metafunc``.""" - - def runtest(self) -> None: - raise RuntimeError("function definitions are not supposed to be run as tests") - - setup = runtest diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/python_api.py b/tests/venv2/lib/python3.11/site-packages/_pytest/python_api.py deleted file mode 100644 index d7f4c2d..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/python_api.py +++ /dev/null @@ -1,922 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import builtins -from collections.abc import Collection -from collections.abc import Mapping -from collections.abc import Sequence -from collections.abc import Sized -from datetime import datetime -from datetime import timedelta -from decimal import Decimal -import math -from numbers import Complex -import pprint -import sys -from typing import Any -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from numpy import ndarray - - -def _compare_approx( - full_object: object, - message_data: Sequence[tuple[str, str, str]], - number_of_elements: int, - different_ids: Sequence[object], - max_abs_diff: float, - max_rel_diff: float, -) -> list[str]: - message_list = list(message_data) - message_list.insert(0, ("Index", "Obtained", "Expected")) - max_sizes = [0, 0, 0] - for index, obtained, expected in message_list: - max_sizes[0] = max(max_sizes[0], len(index)) - max_sizes[1] = max(max_sizes[1], len(obtained)) - max_sizes[2] = max(max_sizes[2], len(expected)) - explanation = [ - f"comparison failed. Mismatched elements: {len(different_ids)} / {number_of_elements}:", - f"Max absolute difference: {max_abs_diff}", - f"Max relative difference: {max_rel_diff}", - ] + [ - f"{indexes:<{max_sizes[0]}} | {obtained:<{max_sizes[1]}} | {expected:<{max_sizes[2]}}" - for indexes, obtained, expected in message_list - ] - return explanation - - -# builtin pytest.approx helper - - -class ApproxBase: - """Provide shared utilities for making approximate comparisons between - numbers or sequences of numbers.""" - - # Tell numpy to use our `__eq__` operator instead of its. - __array_ufunc__ = None - __array_priority__ = 100 - - def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: - __tracebackhide__ = True - self.expected = expected - self.abs = abs - self.rel = rel - self.nan_ok = nan_ok - self._check_type() - - def __repr__(self) -> str: - raise NotImplementedError - - def _repr_compare(self, other_side: Any) -> list[str]: - return [ - "comparison failed", - f"Obtained: {other_side}", - f"Expected: {self}", - ] - - def __eq__(self, actual) -> bool: - return all( - a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) - ) - - def __bool__(self): - __tracebackhide__ = True - raise AssertionError( - "approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?" - ) - - # Ignore type because of https://github.com/python/mypy/issues/4266. - __hash__ = None # type: ignore - - def __ne__(self, actual) -> bool: - return not (actual == self) - - def _approx_scalar(self, x) -> ApproxBase: - if isinstance(x, Decimal): - return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) - if isinstance(x, (datetime, timedelta)): - return ApproxTimedelta(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) - return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) - - def _yield_comparisons(self, actual): - """Yield all the pairs of numbers to be compared. - - This is used to implement the `__eq__` method. - """ - raise NotImplementedError - - def _check_type(self) -> None: - """Raise a TypeError if the expected value is not a valid type.""" - # This is only a concern if the expected value is a sequence. In every - # other case, the approx() function ensures that the expected value has - # a numeric type. For this reason, the default is to do nothing. The - # classes that deal with sequences should reimplement this method to - # raise if there are any non-numeric elements in the sequence. - - -def _recursive_sequence_map(f, x): - """Recursively map a function over a sequence of arbitrary depth""" - if isinstance(x, list | tuple): - seq_type = type(x) - return seq_type(_recursive_sequence_map(f, xi) for xi in x) - elif _is_sequence_like(x): - return [_recursive_sequence_map(f, xi) for xi in x] - else: - return f(x) - - -class ApproxNumpy(ApproxBase): - """Perform approximate comparisons where the expected value is numpy array.""" - - def __repr__(self) -> str: - list_scalars = _recursive_sequence_map( - self._approx_scalar, self.expected.tolist() - ) - return f"approx({list_scalars!r})" - - def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]: - import itertools - import math - - def get_value_from_nested_list( - nested_list: list[Any], nd_index: tuple[Any, ...] - ) -> Any: - """ - Helper function to get the value out of a nested list, given an n-dimensional index. - This mimics numpy's indexing, but for raw nested python lists. - """ - value: Any = nested_list - for i in nd_index: - value = value[i] - return value - - np_array_shape = self.expected.shape - approx_side_as_seq = _recursive_sequence_map( - self._approx_scalar, self.expected.tolist() - ) - - # convert other_side to numpy array to ensure shape attribute is available - other_side_as_array = _as_numpy_array(other_side) - assert other_side_as_array is not None - - if np_array_shape != other_side_as_array.shape: - return [ - "Impossible to compare arrays with different shapes.", - f"Shapes: {np_array_shape} and {other_side_as_array.shape}", - ] - - number_of_elements = self.expected.size - max_abs_diff = -math.inf - max_rel_diff = -math.inf - different_ids = [] - for index in itertools.product(*(range(i) for i in np_array_shape)): - approx_value = get_value_from_nested_list(approx_side_as_seq, index) - other_value = get_value_from_nested_list(other_side_as_array, index) - if approx_value != other_value: - abs_diff = abs(approx_value.expected - other_value) - max_abs_diff = max(max_abs_diff, abs_diff) - if other_value == 0.0: - max_rel_diff = math.inf - else: - max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) - different_ids.append(index) - - message_data = [ - ( - str(index), - str(get_value_from_nested_list(other_side_as_array, index)), - str(get_value_from_nested_list(approx_side_as_seq, index)), - ) - for index in different_ids - ] - return _compare_approx( - self.expected, - message_data, - number_of_elements, - different_ids, - max_abs_diff, - max_rel_diff, - ) - - def __eq__(self, actual) -> bool: - import numpy as np - - # self.expected is supposed to always be an array here. - - if not np.isscalar(actual): - try: - actual = np.asarray(actual) - except Exception as e: - raise TypeError(f"cannot compare '{actual}' to numpy.ndarray") from e - - if not np.isscalar(actual) and actual.shape != self.expected.shape: - return False - - return super().__eq__(actual) - - def _yield_comparisons(self, actual): - import numpy as np - - # `actual` can either be a numpy array or a scalar, it is treated in - # `__eq__` before being passed to `ApproxBase.__eq__`, which is the - # only method that calls this one. - - if np.isscalar(actual): - for i in np.ndindex(self.expected.shape): - yield actual, self.expected[i].item() - else: - for i in np.ndindex(self.expected.shape): - yield actual[i].item(), self.expected[i].item() - - -class ApproxMapping(ApproxBase): - """Perform approximate comparisons where the expected value is a mapping - with numeric values (the keys can be anything).""" - - def __repr__(self) -> str: - return f"approx({ ({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})" - - def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: - import math - - if len(self.expected) != len(other_side): - return [ - "Impossible to compare mappings with different sizes.", - f"Lengths: {len(self.expected)} and {len(other_side)}", - ] - - if self.expected.keys() != other_side.keys(): - return [ - "comparison failed.", - f"Mappings has different keys: expected {self.expected.keys()} but got {other_side.keys()}", - ] - - approx_side_as_map = { - k: self._approx_scalar(v) for k, v in self.expected.items() - } - - number_of_elements = len(approx_side_as_map) - max_abs_diff = -math.inf - max_rel_diff = -math.inf - different_ids = [] - for approx_key, approx_value in approx_side_as_map.items(): - other_value = other_side[approx_key] - if approx_value != other_value: - if approx_value.expected is not None and other_value is not None: - try: - max_abs_diff = max( - max_abs_diff, abs(approx_value.expected - other_value) - ) - if approx_value.expected == 0.0: - max_rel_diff = math.inf - else: - max_rel_diff = max( - max_rel_diff, - abs( - (approx_value.expected - other_value) - / approx_value.expected - ), - ) - except ZeroDivisionError: - pass - different_ids.append(approx_key) - - message_data = [ - (str(key), str(other_side[key]), str(approx_side_as_map[key])) - for key in different_ids - ] - - return _compare_approx( - self.expected, - message_data, - number_of_elements, - different_ids, - max_abs_diff, - max_rel_diff, - ) - - def __eq__(self, actual) -> bool: - try: - if set(actual.keys()) != set(self.expected.keys()): - return False - except AttributeError: - return False - - return super().__eq__(actual) - - def _yield_comparisons(self, actual): - for k in self.expected.keys(): - yield actual[k], self.expected[k] - - def _check_type(self) -> None: - __tracebackhide__ = True - for key, value in self.expected.items(): - if isinstance(value, type(self.expected)): - msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}" - raise TypeError(msg.format(key, value, pprint.pformat(self.expected))) - - -class ApproxSequenceLike(ApproxBase): - """Perform approximate comparisons where the expected value is a sequence of numbers.""" - - def __repr__(self) -> str: - seq_type = type(self.expected) - if seq_type not in (tuple, list): - seq_type = list - return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})" - - def _repr_compare(self, other_side: Sequence[float]) -> list[str]: - import math - - if len(self.expected) != len(other_side): - return [ - "Impossible to compare lists with different sizes.", - f"Lengths: {len(self.expected)} and {len(other_side)}", - ] - - approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected) - - number_of_elements = len(approx_side_as_map) - max_abs_diff = -math.inf - max_rel_diff = -math.inf - different_ids = [] - for i, (approx_value, other_value) in enumerate( - zip(approx_side_as_map, other_side, strict=True) - ): - if approx_value != other_value: - try: - abs_diff = abs(approx_value.expected - other_value) - max_abs_diff = max(max_abs_diff, abs_diff) - # Ignore non-numbers for the diff calculations (#13012). - except TypeError: - pass - else: - if other_value == 0.0: - max_rel_diff = math.inf - else: - max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) - different_ids.append(i) - message_data = [ - (str(i), str(other_side[i]), str(approx_side_as_map[i])) - for i in different_ids - ] - - return _compare_approx( - self.expected, - message_data, - number_of_elements, - different_ids, - max_abs_diff, - max_rel_diff, - ) - - def __eq__(self, actual) -> bool: - try: - if len(actual) != len(self.expected): - return False - except TypeError: - return False - return super().__eq__(actual) - - def _yield_comparisons(self, actual): - return zip(actual, self.expected, strict=True) - - def _check_type(self) -> None: - __tracebackhide__ = True - for index, x in enumerate(self.expected): - if isinstance(x, type(self.expected)): - msg = "pytest.approx() does not support nested data structures: {!r} at index {}\n full sequence: {}" - raise TypeError(msg.format(x, index, pprint.pformat(self.expected))) - - -class ApproxScalar(ApproxBase): - """Perform approximate comparisons where the expected value is a single number.""" - - # Using Real should be better than this Union, but not possible yet: - # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 - - def __repr__(self) -> str: - """Return a string communicating both the expected value and the - tolerance for the comparison being made. - - For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. - """ - # Don't show a tolerance for values that aren't compared using - # tolerances, i.e. non-numerics and infinities. Need to call abs to - # handle complex numbers, e.g. (inf + 1j). - if ( - isinstance(self.expected, bool) - or (not isinstance(self.expected, Complex | Decimal)) - or math.isinf(abs(self.expected) or isinstance(self.expected, bool)) - ): - return str(self.expected) - - # If a sensible tolerance can't be calculated, self.tolerance will - # raise a ValueError. In this case, display '???'. - try: - if 1e-3 <= self.tolerance < 1e3: - vetted_tolerance = f"{self.tolerance:n}" - else: - vetted_tolerance = f"{self.tolerance:.1e}" - - if ( - isinstance(self.expected, Complex) - and self.expected.imag - and not math.isinf(self.tolerance) - ): - vetted_tolerance += " ∠ ±180°" - except ValueError: - vetted_tolerance = "???" - - return f"{self.expected} ± {vetted_tolerance}" - - def __eq__(self, actual) -> bool: - """Return whether the given value is equal to the expected value - within the pre-specified tolerance.""" - - def is_bool(val: Any) -> bool: - # Check if `val` is a native bool or numpy bool. - if isinstance(val, bool): - return True - if np := sys.modules.get("numpy"): - return isinstance(val, np.bool_) - return False - - asarray = _as_numpy_array(actual) - if asarray is not None: - # Call ``__eq__()`` manually to prevent infinite-recursion with - # numpy<1.13. See #3748. - return all(self.__eq__(a) for a in asarray.flat) - - # Short-circuit exact equality, except for bool and np.bool_ - if is_bool(self.expected) and not is_bool(actual): - return False - elif actual == self.expected: - return True - - # If either type is non-numeric, fall back to strict equality. - # NB: we need Complex, rather than just Number, to ensure that __abs__, - # __sub__, and __float__ are defined. Also, consider bool to be - # non-numeric, even though it has the required arithmetic. - if is_bool(self.expected) or not ( - isinstance(self.expected, Complex | Decimal) - and isinstance(actual, Complex | Decimal) - ): - return False - - # Allow the user to control whether NaNs are considered equal to each - # other or not. The abs() calls are for compatibility with complex - # numbers. - if math.isnan(abs(self.expected)): - return self.nan_ok and math.isnan(abs(actual)) - - # Infinity shouldn't be approximately equal to anything but itself, but - # if there's a relative tolerance, it will be infinite and infinity - # will seem approximately equal to everything. The equal-to-itself - # case would have been short circuited above, so here we can just - # return false if the expected value is infinite. The abs() call is - # for compatibility with complex numbers. - if math.isinf(abs(self.expected)): - return False - - # Return true if the two numbers are within the tolerance. - result: bool = abs(self.expected - actual) <= self.tolerance - return result - - __hash__ = None - - @property - def tolerance(self): - """Return the tolerance for the comparison. - - This could be either an absolute tolerance or a relative tolerance, - depending on what the user specified or which would be larger. - """ - - def set_default(x, default): - return x if x is not None else default - - # Figure out what the absolute tolerance should be. ``self.abs`` is - # either None or a value specified by the user. - absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE) - - if absolute_tolerance < 0: - raise ValueError( - f"absolute tolerance can't be negative: {absolute_tolerance}" - ) - if math.isnan(absolute_tolerance): - raise ValueError("absolute tolerance can't be NaN.") - - # If the user specified an absolute tolerance but not a relative one, - # just return the absolute tolerance. - if self.rel is None: - if self.abs is not None: - return absolute_tolerance - - # Figure out what the relative tolerance should be. ``self.rel`` is - # either None or a value specified by the user. This is done after - # we've made sure the user didn't ask for an absolute tolerance only, - # because we don't want to raise errors about the relative tolerance if - # we aren't even going to use it. - relative_tolerance = set_default( - self.rel, self.DEFAULT_RELATIVE_TOLERANCE - ) * abs(self.expected) - - if relative_tolerance < 0: - raise ValueError( - f"relative tolerance can't be negative: {relative_tolerance}" - ) - if math.isnan(relative_tolerance): - raise ValueError("relative tolerance can't be NaN.") - - # Return the larger of the relative and absolute tolerances. - return max(relative_tolerance, absolute_tolerance) - - -class ApproxDecimal(ApproxScalar): - """Perform approximate comparisons where the expected value is a Decimal.""" - - DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") - DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") - - def __repr__(self) -> str: - if isinstance(self.rel, float): - rel = Decimal.from_float(self.rel) - else: - rel = self.rel - - if isinstance(self.abs, float): - abs_ = Decimal.from_float(self.abs) - else: - abs_ = self.abs - - tol_str = "???" - if rel is not None and Decimal("1e-3") <= rel <= Decimal("1e3"): - tol_str = f"{rel:.1e}" - elif abs_ is not None: - tol_str = f"{abs_:.1e}" - - return f"{self.expected} ± {tol_str}" - - -class ApproxTimedelta(ApproxBase): - """Perform approximate comparisons where the expected value is a - datetime or timedelta. - - Requires an explicit tolerance as a timedelta for abs, or a float for rel. - Relative tolerance is not supported for datetime comparisons. - """ - - def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: - __tracebackhide__ = True - if isinstance(expected, datetime) and rel is not None: - raise TypeError( - "pytest.approx() does not support relative tolerance for " - "datetime comparisons. Use abs=timedelta(...) instead." - ) - if nan_ok: - raise TypeError( - "pytest.approx() does not support nan_ok for " - "datetime/timedelta comparisons." - ) - if abs is None and rel is None: - raise TypeError( - "pytest.approx() requires an explicit tolerance for " - "datetime/timedelta comparisons: " - "e.g. approx(expected, abs=timedelta(seconds=1)) " - "or approx(expected, rel=0.01)" - ) - if abs is not None and not isinstance(abs, timedelta): - raise TypeError( - f"absolute tolerance for datetime/timedelta must be a " - f"timedelta, got {type(abs).__name__}" - ) - if abs is not None and abs < timedelta(0): - raise ValueError(f"absolute tolerance can't be negative: {abs}") - if rel is not None: - if not isinstance(rel, (int, float)): - raise TypeError( - f"relative tolerance for timedelta must be a " - f"number, got {type(rel).__name__}" - ) - if rel < 0: - raise ValueError(f"relative tolerance can't be negative: {rel}") - if math.isnan(rel): - raise ValueError("relative tolerance can't be NaN.") - # Compute the effective tolerance. abs_tolerance is a timedelta, rel * expected - # gives a timedelta (timedelta * float works in Python). - abs_tolerance = abs - rel_tolerance = rel * builtins.abs(expected) if rel is not None else None - if abs_tolerance is not None and rel_tolerance is not None: - tolerance = max(abs_tolerance, rel_tolerance) - else: - tolerance = abs_tolerance if abs_tolerance is not None else rel_tolerance - super().__init__(expected, rel=rel, abs=tolerance, nan_ok=False) - - def __repr__(self) -> str: - return f"{self.expected} ± {self.abs}" - - def __eq__(self, actual) -> bool: - try: - return bool(builtins.abs(self.expected - actual) <= self.abs) - except (TypeError, OverflowError): - return False - - def _yield_comparisons(self, actual): - yield actual, self.expected - - def _repr_compare(self, other_side: Any) -> list[str]: - try: - abs_diff = builtins.abs(self.expected - other_side) - except (TypeError, OverflowError): - abs_diff = "N/A" - return [ - "comparison failed", - f"Obtained: {other_side}", - f"Expected: {self.expected} ± {self.abs}", - f"Absolute difference: {abs_diff}", - f"Tolerance: {self.abs}", - ] - - -def approx( - expected: Any, - rel: float | Decimal | timedelta | None = None, - abs: float | Decimal | timedelta | None = None, - nan_ok: bool = False, -) -> ApproxBase: - """Assert that two numbers (or two ordered sequences of numbers) are equal to each other - within some tolerance. - - Due to the :doc:`python:tutorial/floatingpoint`, numbers that we - would intuitively expect to be equal are not always so:: - - >>> 0.1 + 0.2 == 0.3 - False - - This problem is commonly encountered when writing tests, e.g. when making - sure that floating-point values are what you expect them to be. One way to - deal with this problem is to assert that two floating-point numbers are - equal to within some appropriate tolerance:: - - >>> abs((0.1 + 0.2) - 0.3) < 1e-6 - True - - However, comparisons like this are tedious to write and difficult to - understand. Furthermore, absolute comparisons like the one above are - usually discouraged because there's no tolerance that works well for all - situations. ``1e-6`` is good for numbers around ``1``, but too small for - very big numbers and too big for very small ones. It's better to express - the tolerance as a fraction of the expected value, but relative comparisons - like that are even more difficult to write correctly and concisely. - - The ``approx`` class performs floating-point comparisons using a syntax - that's as intuitive as possible:: - - >>> from pytest import approx - >>> 0.1 + 0.2 == approx(0.3) - True - - The same syntax also works for ordered sequences of numbers:: - - >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6)) - True - - ``numpy`` arrays:: - - >>> import numpy as np # doctest: +SKIP - >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP - True - - And for a ``numpy`` array against a scalar:: - - >>> import numpy as np # doctest: +SKIP - >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP - True - - Only ordered sequences are supported, because ``approx`` needs - to infer the relative position of the sequences without ambiguity. This means - ``sets`` and other unordered sequences are not supported. - - Finally, dictionary *values* can also be compared:: - - >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) - True - - The comparison will be true if both mappings have the same keys and their - respective values match the expected tolerances. - - **Tolerances** - - By default, ``approx`` considers numbers within a relative tolerance of - ``1e-6`` (i.e. one part in a million) of its expected value to be equal. - This treatment would lead to surprising results if the expected value was - ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``. - To handle this case less surprisingly, ``approx`` also considers numbers - within an absolute tolerance of ``1e-12`` of its expected value to be - equal. Infinity and NaN are special cases. Infinity is only considered - equal to itself, regardless of the relative tolerance. NaN is not - considered equal to anything by default, but you can make it be equal to - itself by setting the ``nan_ok`` argument to True. (This is meant to - facilitate comparing arrays that use NaN to mean "no data".) - - Both the relative and absolute tolerances can be changed by passing - arguments to the ``approx`` constructor:: - - >>> 1.0001 == approx(1) - False - >>> 1.0001 == approx(1, rel=1e-3) - True - >>> 1.0001 == approx(1, abs=1e-3) - True - - If you specify ``abs`` but not ``rel``, the comparison will not consider - the relative tolerance at all. In other words, two numbers that are within - the default relative tolerance of ``1e-6`` will still be considered unequal - if they exceed the specified absolute tolerance. If you specify both - ``abs`` and ``rel``, the numbers will be considered equal if either - tolerance is met:: - - >>> 1 + 1e-8 == approx(1) - True - >>> 1 + 1e-8 == approx(1, abs=1e-12) - False - >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12) - True - - **Non-numeric types** - - You can also use ``approx`` to compare non-numeric types, or dicts and - sequences containing non-numeric types, in which case it falls back to - strict equality. This can be useful for comparing dicts and sequences that - can contain optional values:: - - >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None}) - True - >>> [None, 1.0000005] == approx([None,1]) - True - >>> ["foo", 1.0000005] == approx([None,1]) - False - - **datetime and timedelta** - - You can also use ``approx`` to compare :class:`~datetime.datetime` and - :class:`~datetime.timedelta` objects by specifying an absolute tolerance - as a :class:`~datetime.timedelta`:: - - >>> from datetime import datetime, timedelta - >>> dt1 = datetime(2024, 1, 1, 12, 0, 0) - >>> dt2 = datetime(2024, 1, 1, 12, 0, 0, 500000) - >>> dt1 == approx(dt2, abs=timedelta(seconds=1)) - True - - Note that ``rel`` is not supported for datetime comparisons. - For timedelta comparisons, ``rel`` is a number (not a timedelta) that - represents a relative tolerance -- a fraction of the expected value. - ``abs`` must be a ``timedelta`` object in both cases. - - .. versionadded:: 8.4 - - If you're thinking about using ``approx``, then you might want to know how - it compares to other good ways of comparing floating-point numbers. All of - these algorithms are based on relative and absolute tolerances and should - agree for the most part, but they do have meaningful differences: - - - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative - tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute - tolerance is met. Because the relative tolerance is calculated w.r.t. - both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor - ``b`` is a "reference value"). You have to specify an absolute tolerance - if you want to compare to ``0.0`` because there is no tolerance by - default. More information: :py:func:`math.isclose`. - - - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference - between ``a`` and ``b`` is less that the sum of the relative tolerance - w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance - is only calculated w.r.t. ``b``, this test is asymmetric and you can - think of ``b`` as the reference value. Support for comparing sequences - is provided by :py:func:`numpy.allclose`. More information: - :std:doc:`numpy:reference/generated/numpy.isclose`. - - - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b`` - are within an absolute tolerance of ``1e-7``. No relative tolerance is - considered , so this function is not appropriate for very large or very - small numbers. Also, it's only available in subclasses of ``unittest.TestCase`` - and it's ugly because it doesn't follow PEP8. More information: - :py:meth:`unittest.TestCase.assertAlmostEqual`. - - - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative - tolerance is met w.r.t. ``b`` or if the absolute tolerance is met. - Because the relative tolerance is only calculated w.r.t. ``b``, this test - is asymmetric and you can think of ``b`` as the reference value. In the - special case that you explicitly specify an absolute tolerance but not a - relative tolerance, only the absolute tolerance is considered. - - .. note:: - - ``approx`` can handle numpy arrays, but we recommend the - specialised test helpers in :std:doc:`numpy:reference/routines.testing` - if you need support for comparisons, NaNs, or ULP-based tolerances. - - To match strings using regex, you can use - `Matches `_ - from the - `re_assert package `_. - - - .. note:: - - Unlike built-in equality, this function considers - booleans unequal to numeric zero or one. For example:: - - >>> 1 == approx(True) - False - - .. warning:: - - .. versionchanged:: 3.2 - - In order to avoid inconsistent behavior, :py:exc:`TypeError` is - raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons. - The example below illustrates the problem:: - - assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10) - assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10) - - In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)`` - to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to - comparison. This is because the call hierarchy of rich comparisons - follows a fixed behavior. More information: :py:meth:`object.__ge__` - - .. versionchanged:: 3.7.1 - ``approx`` raises ``TypeError`` when it encounters a dict value or - sequence element of non-numeric type. - - .. versionchanged:: 6.1.0 - ``approx`` falls back to strict equality for non-numeric types instead - of raising ``TypeError``. - """ - # Delegate the comparison to a class that knows how to deal with the type - # of the expected value (e.g. int, float, list, dict, numpy.array, etc). - # - # The primary responsibility of these classes is to implement ``__eq__()`` - # and ``__repr__()``. The former is used to actually check if some - # "actual" value is equivalent to the given expected value within the - # allowed tolerance. The latter is used to show the user the expected - # value and tolerance, in the case that a test failed. - # - # The actual logic for making approximate comparisons can be found in - # ApproxScalar, which is used to compare individual numbers. All of the - # other Approx classes eventually delegate to this class. The ApproxBase - # class provides some convenient methods and overloads, but isn't really - # essential. - - __tracebackhide__ = True - - if isinstance(expected, Decimal): - cls: type[ApproxBase] = ApproxDecimal - elif isinstance(expected, Mapping): - cls = ApproxMapping - elif (np_array := _as_numpy_array(expected)) is not None: - expected = np_array - cls = ApproxNumpy - elif _is_sequence_like(expected): - cls = ApproxSequenceLike - elif isinstance(expected, Collection) and not isinstance(expected, str | bytes): - msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" - raise TypeError(msg) - elif isinstance(expected, (datetime, timedelta)): - cls = ApproxTimedelta - else: - cls = ApproxScalar - - return cls(expected, rel, abs, nan_ok) - - -def _is_sequence_like(expected: object) -> bool: - return ( - hasattr(expected, "__getitem__") - and isinstance(expected, Sized) - and not isinstance(expected, str | bytes) - ) - - -def _as_numpy_array(obj: object) -> ndarray | None: - """ - Return an ndarray if the given object is implicitly convertible to ndarray, - and numpy is already imported, otherwise None. - """ - np: Any = sys.modules.get("numpy") - if np is not None: - # avoid infinite recursion on numpy scalars, which have __array__ - if np.isscalar(obj): - return None - elif isinstance(obj, np.ndarray): - return obj - elif hasattr(obj, "__array__") or hasattr(obj, "__array_interface__"): - return np.asarray(obj) - return None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/raises.py b/tests/venv2/lib/python3.11/site-packages/_pytest/raises.py deleted file mode 100644 index ef91cc2..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/raises.py +++ /dev/null @@ -1,1505 +0,0 @@ -from __future__ import annotations - -from abc import ABC -from abc import abstractmethod -import re -from re import Pattern -import sys -from textwrap import indent -from typing import Any -from typing import cast -from typing import final -from typing import Generic -from typing import get_args -from typing import get_origin -from typing import Literal -from typing import overload -from typing import TYPE_CHECKING -import warnings - -from _pytest._code import ExceptionInfo -from _pytest._code.code import stringify_exception -from _pytest.outcomes import fail -from _pytest.warning_types import PytestWarning - - -if TYPE_CHECKING: - from collections.abc import Callable - from collections.abc import Sequence - - # for some reason Sphinx does not play well with 'from types import TracebackType' - import types - from typing import TypeGuard - - from typing_extensions import ParamSpec - from typing_extensions import TypeVar - - P = ParamSpec("P") - - # this conditional definition is because we want to allow a TypeVar default - BaseExcT_co_default = TypeVar( - "BaseExcT_co_default", - bound=BaseException, - default=BaseException, - covariant=True, - ) - - # Use short name because it shows up in docs. - E = TypeVar("E", bound=BaseException, default=BaseException) -else: - from typing import TypeVar - - BaseExcT_co_default = TypeVar( - "BaseExcT_co_default", bound=BaseException, covariant=True - ) - -# RaisesGroup doesn't work with a default. -BaseExcT_co = TypeVar("BaseExcT_co", bound=BaseException, covariant=True) -BaseExcT_1 = TypeVar("BaseExcT_1", bound=BaseException) -BaseExcT_2 = TypeVar("BaseExcT_2", bound=BaseException) -ExcT_1 = TypeVar("ExcT_1", bound=Exception) -ExcT_2 = TypeVar("ExcT_2", bound=Exception) - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - from exceptiongroup import ExceptionGroup - - -# String patterns default to including the unicode flag. -_REGEX_NO_FLAGS = re.compile(r"").flags - - -# pytest.raises helper -@overload -def raises( - expected_exception: type[E] | tuple[type[E], ...], - *, - match: str | re.Pattern[str] | None = ..., - check: Callable[[E], bool] = ..., -) -> RaisesExc[E]: ... - - -@overload -def raises( - *, - match: str | re.Pattern[str], - # If exception_type is not provided, check() must do any typechecks itself. - check: Callable[[BaseException], bool] = ..., -) -> RaisesExc[BaseException]: ... - - -@overload -def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException]: ... - - -@overload -def raises( - expected_exception: type[E] | tuple[type[E], ...], - func: Callable[P, object], - *args: P.args, - **kwargs: P.kwargs, -) -> ExceptionInfo[E]: ... - - -def raises( - expected_exception: type[E] | tuple[type[E], ...] | None = None, - func: Callable[P, object] | None = None, - *args: Any, - **kwargs: Any, -) -> RaisesExc[BaseException] | ExceptionInfo[E]: - r"""Assert that a code block/function call raises an exception type, or one of its subclasses. - - :param expected_exception: - The expected exception type, or a tuple if one of multiple possible - exception types are expected. Note that subclasses of the passed exceptions - will also match. - - This is not a required parameter, you may opt to only use ``match`` and/or - ``check`` for verifying the raised exception. - - :kwparam str | re.Pattern[str] | None match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception and its :pep:`678` `__notes__` - using :func:`re.search`. - - To match a literal string that may contain :ref:`special characters - `, the pattern can first be escaped with :func:`re.escape`. - - (This is only used when ``pytest.raises`` is used as a context manager, - and passed through to the function otherwise. - When using ``pytest.raises`` as a function, you can use: - ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - - :kwparam Callable[[BaseException], bool] check: - - .. versionadded:: 8.4 - - If specified, a callable that will be called with the exception as a parameter - after checking the type and the match regex if specified. - If it returns ``True`` it will be considered a match, if not it will - be considered a failed match. - - - Use ``pytest.raises`` as a context manager, which will capture the exception of the given - type, or any of its subclasses:: - - >>> import pytest - >>> with pytest.raises(ZeroDivisionError): - ... 1/0 - - If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example - above), or no exception at all, the check will fail instead. - - You can also use the keyword argument ``match`` to assert that the - exception matches a text or regex:: - - >>> with pytest.raises(ValueError, match='must be 0 or None'): - ... raise ValueError("value must be 0 or None") - - >>> with pytest.raises(ValueError, match=r'must be \d+$'): - ... raise ValueError("value must be 42") - - The ``match`` argument searches the formatted exception string, which includes any - `PEP-678 `__ ``__notes__``: - - >>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP - ... e = ValueError("value must be 42") - ... e.add_note("had a note added") - ... raise e - - The ``check`` argument, if provided, must return True when passed the raised exception - for the match to be successful, otherwise an :exc:`AssertionError` is raised. - - >>> import errno - >>> with pytest.raises(OSError, check=lambda e: e.errno == errno.EACCES): - ... raise OSError(errno.EACCES, "no permission to view") - - The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the - details of the captured exception:: - - >>> with pytest.raises(ValueError) as exc_info: - ... raise ValueError("value must be 42") - >>> assert exc_info.type is ValueError - >>> assert exc_info.value.args[0] == "value must be 42" - - .. warning:: - - Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this:: - - # Careful, this will catch ANY exception raised. - with pytest.raises(Exception): - some_function() - - Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide - real bugs, where the user wrote this expecting a specific exception, but some other exception is being - raised due to a bug introduced during a refactoring. - - Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch - **any** exception raised. - - .. note:: - - When using ``pytest.raises`` as a context manager, it's worthwhile to - note that normal context manager rules apply and that the exception - raised *must* be the final line in the scope of the context manager. - Lines of code after that, within the scope of the context manager will - not be executed. For example:: - - >>> value = 15 - >>> with pytest.raises(ValueError) as exc_info: - ... if value > 10: - ... raise ValueError("value must be <= 10") - ... assert exc_info.type is ValueError # This will not execute. - - Instead, the following approach must be taken (note the difference in - scope):: - - >>> with pytest.raises(ValueError) as exc_info: - ... if value > 10: - ... raise ValueError("value must be <= 10") - ... - >>> assert exc_info.type is ValueError - - **Expecting exception groups** - - When expecting exceptions wrapped in :exc:`BaseExceptionGroup` or - :exc:`ExceptionGroup`, you should instead use :class:`pytest.RaisesGroup`. - - **Using with** ``pytest.mark.parametrize`` - - When using :ref:`pytest.mark.parametrize ref` - it is possible to parametrize tests such that - some runs raise an exception and others do not. - - See :ref:`parametrizing_conditional_raising` for an example. - - .. seealso:: - - :ref:`assertraises` for more examples and detailed discussion. - - .. note:: - Similar to caught exception objects in Python, explicitly clearing - local references to returned ``ExceptionInfo`` objects can - help the Python interpreter speed up its garbage collection. - - Clearing those references breaks a reference cycle - (``ExceptionInfo`` --> caught exception --> frame stack raising - the exception --> current frame stack --> local variables --> - ``ExceptionInfo``) which makes Python keep all objects referenced - from that cycle (including all local variables in the current - frame) alive until the next cyclic garbage collection run. - More detailed information can be found in the official Python - documentation for :ref:`the try statement `. - """ - __tracebackhide__ = True - - if func is None and not args: - if set(kwargs) - {"match", "check", "expected_exception"}: - msg = "Unexpected keyword arguments passed to pytest.raises: " - msg += ", ".join(sorted(kwargs)) - msg += "\nUse context-manager form instead?" - raise TypeError(msg) - - if expected_exception is None: - return RaisesExc(**kwargs) - return RaisesExc(expected_exception, **kwargs) - - if not expected_exception: - raise ValueError( - f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " - f"Raising exceptions is already understood as failing the test, so you don't need " - f"any special code to say 'this should never raise an exception'." - ) - if not callable(func): - raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") - with RaisesExc(expected_exception) as excinfo: - func(*args, **kwargs) - try: - return excinfo - finally: - del excinfo - - -# note: RaisesExc/RaisesGroup uses fail() internally, so this alias -# indicates (to [internal] plugins?) that `pytest.raises` will -# raise `_pytest.outcomes.Failed`, where -# `outcomes.Failed is outcomes.fail.Exception is raises.Exception` -# note: this is *not* the same as `_pytest.main.Failed` -# note: mypy does not recognize this attribute, and it's not possible -# to use a protocol/decorator like the others in outcomes due to -# https://github.com/python/mypy/issues/18715 -raises.Exception = fail.Exception # type: ignore[attr-defined] - - -def _match_pattern(match: Pattern[str]) -> str | Pattern[str]: - """Helper function to remove redundant `re.compile` calls when printing regex""" - return match.pattern if match.flags == _REGEX_NO_FLAGS else match - - -def repr_callable(fun: Callable[[BaseExcT_1], bool]) -> str: - """Get the repr of a ``check`` parameter. - - Split out so it can be monkeypatched (e.g. by hypothesis) - """ - return repr(fun) - - -def backquote(s: str) -> str: - return "`" + s + "`" - - -def _exception_type_name( - e: type[BaseException] | tuple[type[BaseException], ...], -) -> str: - if isinstance(e, type): - return e.__name__ - if len(e) == 1: - return e[0].__name__ - return "(" + ", ".join(ee.__name__ for ee in e) + ")" - - -def _check_raw_type( - expected_type: type[BaseException] | tuple[type[BaseException], ...] | None, - exception: BaseException, -) -> str | None: - if expected_type is None or expected_type == (): - return None - - if not isinstance( - exception, - expected_type, - ): - actual_type_str = backquote(_exception_type_name(type(exception)) + "()") - expected_type_str = backquote(_exception_type_name(expected_type)) - if ( - isinstance(exception, BaseExceptionGroup) - and isinstance(expected_type, type) - and not issubclass(expected_type, BaseExceptionGroup) - ): - return f"Unexpected nested {actual_type_str}, expected {expected_type_str}" - return f"{actual_type_str} is not an instance of {expected_type_str}" - return None - - -def is_fully_escaped(s: str) -> bool: - # we know we won't compile with re.VERBOSE, so whitespace doesn't need to be escaped - metacharacters = "{}()+.*?^$[]|" - # Strip all escape sequences (backslash + any char), then check if any - # metacharacter remains unescaped in the resulting string. - stripped = re.sub(r"\\.", "", s) - return not any(c in metacharacters for c in stripped) - - -def unescape(s: str) -> str: - return re.sub(r"\\([{}()+-.*?^$\[\]\s\\|])", r"\1", s) - - -# These classes conceptually differ from ExceptionInfo in that ExceptionInfo is tied, and -# constructed from, a particular exception - whereas these are constructed with expected -# exceptions, and later allow matching towards particular exceptions. -# But there's overlap in `ExceptionInfo.match` and `AbstractRaises._check_match`, as with -# `AbstractRaises.matches` and `ExceptionInfo.errisinstance`+`ExceptionInfo.group_contains`. -# The interaction between these classes should perhaps be improved. -class AbstractRaises(ABC, Generic[BaseExcT_co]): - """ABC with common functionality shared between RaisesExc and RaisesGroup""" - - def __init__( - self, - *, - match: str | Pattern[str] | None, - check: Callable[[BaseExcT_co], bool] | None, - ) -> None: - if isinstance(match, str): - # juggle error in order to avoid context to fail (necessary?) - re_error = None - try: - self.match: Pattern[str] | None = re.compile(match) - except re.error as e: - re_error = e - if re_error is not None: - fail(f"Invalid regex pattern provided to 'match': {re_error}") - if match == "": - warnings.warn( - PytestWarning( - "matching against an empty string will *always* pass. If you want " - "to check for an empty message you need to pass '^$'. If you don't " - "want to match you should pass `None` or leave out the parameter." - ), - stacklevel=2, - ) - else: - self.match = match - - # check if this is a fully escaped regex and has ^$ to match fully - # in which case we can do a proper diff on error - self.rawmatch: str | None = None - if isinstance(match, str) or ( - isinstance(match, Pattern) and match.flags == _REGEX_NO_FLAGS - ): - if isinstance(match, Pattern): - match = match.pattern - if ( - match - and match[0] == "^" - and match[-1] == "$" - and is_fully_escaped(match[1:-1]) - ): - self.rawmatch = unescape(match[1:-1]) - - self.check = check - self._fail_reason: str | None = None - - # used to suppress repeated printing of `repr(self.check)` - self._nested: bool = False - - # set in self._parse_exc - self.is_baseexception = False - - def _parse_exc( - self, exc: type[BaseExcT_1] | types.GenericAlias, expected: str - ) -> type[BaseExcT_1]: - if isinstance(exc, type) and issubclass(exc, BaseException): - if not issubclass(exc, Exception): - self.is_baseexception = True - return exc - # because RaisesGroup does not support variable number of exceptions there's - # still a use for RaisesExc(ExceptionGroup[Exception]). - origin_exc: type[BaseException] | None = get_origin(exc) - if origin_exc and issubclass(origin_exc, BaseExceptionGroup): - exc_type = get_args(exc)[0] - if ( - issubclass(origin_exc, ExceptionGroup) and exc_type in (Exception, Any) - ) or ( - issubclass(origin_exc, BaseExceptionGroup) - and exc_type in (BaseException, Any) - ): - if not issubclass(origin_exc, ExceptionGroup): - self.is_baseexception = True - return cast(type[BaseExcT_1], origin_exc) - else: - raise ValueError( - f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` " - f"are accepted as generic types but got `{exc}`. " - f"As `raises` will catch all instances of the specified group regardless of the " - f"generic argument specific nested exceptions has to be checked " - f"with `RaisesGroup`." - ) - # unclear if the Type/ValueError distinction is even helpful here - msg = f"Expected {expected}, but got " - if isinstance(exc, type): # type: ignore[unreachable] - raise ValueError(msg + f"{exc.__name__!r}") - if isinstance(exc, BaseException): # type: ignore[unreachable] - raise TypeError(msg + f"an exception instance: {type(exc).__name__}") - raise TypeError(msg + repr(type(exc).__name__)) - - @property - def fail_reason(self) -> str | None: - """Set after a call to :meth:`matches` to give a human-readable reason for why the match failed. - When used as a context manager the string will be printed as the reason for the - test failing.""" - return self._fail_reason - - def _check_check( - self: AbstractRaises[BaseExcT_1], - exception: BaseExcT_1, - ) -> bool: - if self.check is None: - return True - - if self.check(exception): - return True - - check_repr = "" if self._nested else " " + repr_callable(self.check) - self._fail_reason = f"check{check_repr} did not return True" - return False - - # TODO: harmonize with ExceptionInfo.match - def _check_match(self, e: BaseException) -> bool: - if self.match is None or re.search( - self.match, - stringified_exception := stringify_exception( - e, include_subexception_msg=False - ), - ): - return True - - # if we're matching a group, make sure we're explicit to reduce confusion - # if they're trying to match an exception contained within the group - maybe_specify_type = ( - f" the `{_exception_type_name(type(e))}()`" - if isinstance(e, BaseExceptionGroup) - else "" - ) - if isinstance(self.rawmatch, str): - from _pytest.assertion.compare_text import _diff_text - from _pytest.assertion.highlight import dummy_highlighter - from _pytest.assertion.util import _config - from _pytest.config import Config - - verbose = ( - _config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - if _config is not None - else 0 - ) - diff = list( - _diff_text( - self.rawmatch, stringified_exception, dummy_highlighter, verbose - ) - ) - self._fail_reason = ("\n" if diff[0][0] == "-" else "") + "\n".join(diff) - return False - - self._fail_reason = ( - f"Regex pattern did not match{maybe_specify_type}.\n" - f" Expected regex: {_match_pattern(self.match)!r}\n" - f" Actual message: {stringified_exception!r}" - ) - if _match_pattern(self.match) == stringified_exception: - self._fail_reason += "\n Did you mean to `re.escape()` the regex?" - return False - - @abstractmethod - def matches( - self: AbstractRaises[BaseExcT_1], exception: BaseException - ) -> TypeGuard[BaseExcT_1]: - """Check if an exception matches the requirements of this AbstractRaises. - If it fails, :meth:`AbstractRaises.fail_reason` should be set. - """ - - -@final -class RaisesExc(AbstractRaises[BaseExcT_co_default]): - """ - .. versionadded:: 8.4 - - - This is the class constructed when calling :func:`pytest.raises`, but may be used - directly as a helper class with :class:`RaisesGroup` when you want to specify - requirements on sub-exceptions. - - You don't need this if you only want to specify the type, since :class:`RaisesGroup` - accepts ``type[BaseException]``. - - :param type[BaseException] | tuple[type[BaseException]] | None expected_exception: - The expected type, or one of several possible types. - May be ``None`` in order to only make use of ``match`` and/or ``check`` - - The type is checked with :func:`isinstance`, and does not need to be an exact match. - If that is wanted you can use the ``check`` parameter. - - :kwparam str | Pattern[str] match: - A regex to match. - - :kwparam Callable[[BaseException], bool] check: - If specified, a callable that will be called with the exception as a parameter - after checking the type and the match regex if specified. - If it returns ``True`` it will be considered a match, if not it will - be considered a failed match. - - :meth:`RaisesExc.matches` can also be used standalone to check individual exceptions. - - Examples:: - - with RaisesGroup(RaisesExc(ValueError, match="string")) - ... - with RaisesGroup(RaisesExc(check=lambda x: x.args == (3, "hello"))): - ... - with RaisesGroup(RaisesExc(check=lambda x: type(x) is ValueError)): - ... - """ - - # Trio bundled hypothesis monkeypatching, we will probably instead assume that - # hypothesis will handle that in their pytest plugin by the time this is released. - # Alternatively we could add a version of get_pretty_function_description ourselves - # https://github.com/HypothesisWorks/hypothesis/blob/8ced2f59f5c7bea3344e35d2d53e1f8f8eb9fcd8/hypothesis-python/src/hypothesis/internal/reflection.py#L439 - - # At least one of the three parameters must be passed. - @overload - def __init__( - self, - expected_exception: ( - type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] - ), - /, - *, - match: str | Pattern[str] | None = ..., - check: Callable[[BaseExcT_co_default], bool] | None = ..., - ) -> None: ... - - @overload - def __init__( - self: RaisesExc[BaseException], # Give E a value. - /, - *, - match: str | Pattern[str] | None, - # If exception_type is not provided, check() must do any typechecks itself. - check: Callable[[BaseException], bool] | None = ..., - ) -> None: ... - - @overload - def __init__(self, /, *, check: Callable[[BaseException], bool]) -> None: ... - - def __init__( - self, - expected_exception: ( - type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] | None - ) = None, - /, - *, - match: str | Pattern[str] | None = None, - check: Callable[[BaseExcT_co_default], bool] | None = None, - ): - super().__init__(match=match, check=check) - if isinstance(expected_exception, tuple): - expected_exceptions = expected_exception - elif expected_exception is None: - expected_exceptions = () - else: - expected_exceptions = (expected_exception,) - - if (expected_exceptions == ()) and match is None and check is None: - raise ValueError("You must specify at least one parameter to match on.") - - self.expected_exceptions = tuple( - self._parse_exc(e, expected="a BaseException type") - for e in expected_exceptions - ) - - self._just_propagate = False - - def matches( - self, - exception: BaseException | None, - ) -> TypeGuard[BaseExcT_co_default]: - """Check if an exception matches the requirements of this :class:`RaisesExc`. - If it fails, :attr:`RaisesExc.fail_reason` will be set. - - Examples:: - - assert RaisesExc(ValueError).matches(my_exception): - # is equivalent to - assert isinstance(my_exception, ValueError) - - # this can be useful when checking e.g. the ``__cause__`` of an exception. - with pytest.raises(ValueError) as excinfo: - ... - assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__) - # above line is equivalent to - assert isinstance(excinfo.value.__cause__, SyntaxError) - assert re.search("foo", str(excinfo.value.__cause__) - - """ - self._just_propagate = False - if exception is None: - self._fail_reason = "exception is None" - return False - if not self._check_type(exception): - self._just_propagate = True - return False - - if not self._check_match(exception): - return False - - return self._check_check(exception) - - def __repr__(self) -> str: - parameters = [] - if self.expected_exceptions: - parameters.append(_exception_type_name(self.expected_exceptions)) - if self.match is not None: - # If no flags were specified, discard the redundant re.compile() here. - parameters.append( - f"match={_match_pattern(self.match)!r}", - ) - if self.check is not None: - parameters.append(f"check={repr_callable(self.check)}") - return f"RaisesExc({', '.join(parameters)})" - - def _check_type(self, exception: BaseException) -> TypeGuard[BaseExcT_co_default]: - self._fail_reason = _check_raw_type(self.expected_exceptions, exception) - return self._fail_reason is None - - def __enter__(self) -> ExceptionInfo[BaseExcT_co_default]: - self.excinfo: ExceptionInfo[BaseExcT_co_default] = ExceptionInfo.for_later() - return self.excinfo - - # TODO: move common code into superclass - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> bool: - __tracebackhide__ = True - if exc_type is None: - if not self.expected_exceptions: - fail("DID NOT RAISE any exception") - if len(self.expected_exceptions) == 1: - fail(f"DID NOT RAISE {self.expected_exceptions[0].__name__}") - else: - names = ", ".join(x.__name__ for x in self.expected_exceptions) - fail(f"DID NOT RAISE any of ({names})") - - assert self.excinfo is not None, ( - "Internal error - should have been constructed in __enter__" - ) - - if not self.matches(exc_val): - if self._just_propagate: - return False - raise AssertionError(self._fail_reason) from None - - # Cast to narrow the exception type now that it's verified.... - # even though the TypeGuard in self.matches should be narrowing - exc_info = cast( - "tuple[type[BaseExcT_co_default], BaseExcT_co_default, types.TracebackType]", - (exc_type, exc_val, exc_tb), - ) - self.excinfo.fill_unfilled(exc_info) - return True - - -@final -class RaisesGroup(AbstractRaises[BaseExceptionGroup[BaseExcT_co]]): - """ - .. versionadded:: 8.4 - - Contextmanager for checking for an expected :exc:`ExceptionGroup`. - This works similar to :func:`pytest.raises`, but allows for specifying the structure of an :exc:`ExceptionGroup`. - :meth:`ExceptionInfo.group_contains` also tries to handle exception groups, - but it is very bad at checking that you *didn't* get unexpected exceptions. - - The catching behaviour differs from :ref:`except* `, being much - stricter about the structure by default. - By using ``allow_unwrapped=True`` and ``flatten_subgroups=True`` you can match - :ref:`except* ` fully when expecting a single exception. - - :param args: - Any number of exception types, :class:`RaisesGroup` or :class:`RaisesExc` - to specify the exceptions contained in this exception. - All specified exceptions must be present in the raised group, *and no others*. - - If you expect a variable number of exceptions you need to use - :func:`pytest.raises(ExceptionGroup) ` and manually check - the contained exceptions. Consider making use of :meth:`RaisesExc.matches`. - - It does not care about the order of the exceptions, so - ``RaisesGroup(ValueError, TypeError)`` - is equivalent to - ``RaisesGroup(TypeError, ValueError)``. - :kwparam str | re.Pattern[str] | None match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception group and its :pep:`678` `__notes__` - using :func:`re.search`. - - To match a literal string that may contain :ref:`special characters - `, the pattern can first be escaped with :func:`re.escape`. - - Note that " (5 subgroups)" will be stripped from the ``repr`` before matching. - :kwparam Callable[[E], bool] check: - If specified, a callable that will be called with the group as a parameter - after successfully matching the expected exceptions. If it returns ``True`` - it will be considered a match, if not it will be considered a failed match. - :kwparam bool allow_unwrapped: - If expecting a single exception or :class:`RaisesExc` it will match even - if the exception is not inside an exceptiongroup. - - Using this together with ``match``, ``check`` or expecting multiple exceptions - will raise an error. - :kwparam bool flatten_subgroups: - "flatten" any groups inside the raised exception group, extracting all exceptions - inside any nested groups, before matching. Without this it expects you to - fully specify the nesting structure by passing :class:`RaisesGroup` as expected - parameter. - - Examples:: - - with RaisesGroup(ValueError): - raise ExceptionGroup("", (ValueError(),)) - # match - with RaisesGroup( - ValueError, - ValueError, - RaisesExc(TypeError, match="^expected int$"), - match="^my group$", - ): - raise ExceptionGroup( - "my group", - [ - ValueError(), - TypeError("expected int"), - ValueError(), - ], - ) - # check - with RaisesGroup( - KeyboardInterrupt, - match="^hello$", - check=lambda x: isinstance(x.__cause__, ValueError), - ): - raise BaseExceptionGroup("hello", [KeyboardInterrupt()]) from ValueError - # nested groups - with RaisesGroup(RaisesGroup(ValueError)): - raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) - - # flatten_subgroups - with RaisesGroup(ValueError, flatten_subgroups=True): - raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) - - # allow_unwrapped - with RaisesGroup(ValueError, allow_unwrapped=True): - raise ValueError - - - :meth:`RaisesGroup.matches` can also be used directly to check a standalone exception group. - - - The matching algorithm is greedy, which means cases such as this may fail:: - - with RaisesGroup(ValueError, RaisesExc(ValueError, match="hello")): - raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye"))) - - even though it generally does not care about the order of the exceptions in the group. - To avoid the above you should specify the first :exc:`ValueError` with a :class:`RaisesExc` as well. - - .. note:: - When raised exceptions don't match the expected ones, you'll get a detailed error - message explaining why. This includes ``repr(check)`` if set, which in Python can be - overly verbose, showing memory locations etc etc. - - If installed and imported (in e.g. ``conftest.py``), the ``hypothesis`` library will - monkeypatch this output to provide shorter & more readable repr's. - """ - - # allow_unwrapped=True requires: singular exception, exception not being - # RaisesGroup instance, match is None, check is None - @overload - def __init__( - self, - expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co], - /, - *, - allow_unwrapped: Literal[True], - flatten_subgroups: bool = False, - ) -> None: ... - - # flatten_subgroups = True also requires no nested RaisesGroup - @overload - def __init__( - self, - expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co], - /, - *other_exceptions: type[BaseExcT_co] | RaisesExc[BaseExcT_co], - flatten_subgroups: Literal[True], - match: str | Pattern[str] | None = None, - check: Callable[[BaseExceptionGroup[BaseExcT_co]], bool] | None = None, - ) -> None: ... - - # simplify the typevars if possible (the following 3 are equivalent but go simpler->complicated) - # ... the first handles RaisesGroup[ValueError], the second RaisesGroup[ExceptionGroup[ValueError]], - # the third RaisesGroup[ValueError | ExceptionGroup[ValueError]]. - # ... otherwise, we will get results like RaisesGroup[ValueError | ExceptionGroup[Never]] (I think) - # (technically correct but misleading) - @overload - def __init__( - self: RaisesGroup[ExcT_1], - expected_exception: type[ExcT_1] | RaisesExc[ExcT_1], - /, - *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1], - match: str | Pattern[str] | None = None, - check: Callable[[ExceptionGroup[ExcT_1]], bool] | None = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[ExceptionGroup[ExcT_2]], - expected_exception: RaisesGroup[ExcT_2], - /, - *other_exceptions: RaisesGroup[ExcT_2], - match: str | Pattern[str] | None = None, - check: Callable[[ExceptionGroup[ExceptionGroup[ExcT_2]]], bool] | None = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[ExcT_1 | ExceptionGroup[ExcT_2]], - expected_exception: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2], - /, - *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2], - match: str | Pattern[str] | None = None, - check: ( - Callable[[ExceptionGroup[ExcT_1 | ExceptionGroup[ExcT_2]]], bool] | None - ) = None, - ) -> None: ... - - # same as the above 3 but handling BaseException - @overload - def __init__( - self: RaisesGroup[BaseExcT_1], - expected_exception: type[BaseExcT_1] | RaisesExc[BaseExcT_1], - /, - *other_exceptions: type[BaseExcT_1] | RaisesExc[BaseExcT_1], - match: str | Pattern[str] | None = None, - check: Callable[[BaseExceptionGroup[BaseExcT_1]], bool] | None = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[BaseExceptionGroup[BaseExcT_2]], - expected_exception: RaisesGroup[BaseExcT_2], - /, - *other_exceptions: RaisesGroup[BaseExcT_2], - match: str | Pattern[str] | None = None, - check: ( - Callable[[BaseExceptionGroup[BaseExceptionGroup[BaseExcT_2]]], bool] | None - ) = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]], - expected_exception: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - /, - *other_exceptions: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - match: str | Pattern[str] | None = None, - check: ( - Callable[ - [BaseExceptionGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]]], - bool, - ] - | None - ) = None, - ) -> None: ... - - def __init__( - self: RaisesGroup[ExcT_1 | BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]], - expected_exception: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - /, - *other_exceptions: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - allow_unwrapped: bool = False, - flatten_subgroups: bool = False, - match: str | Pattern[str] | None = None, - check: ( - Callable[[BaseExceptionGroup[BaseExcT_1]], bool] - | Callable[[ExceptionGroup[ExcT_1]], bool] - | None - ) = None, - ): - # The type hint on the `self` and `check` parameters uses different formats - # that are *very* hard to reconcile while adhering to the overloads, so we cast - # it to avoid an error when passing it to super().__init__ - check = cast( - "Callable[[BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]], bool]", - check, - ) - super().__init__(match=match, check=check) - self.allow_unwrapped = allow_unwrapped - self.flatten_subgroups: bool = flatten_subgroups - self.is_baseexception = False - - if allow_unwrapped and other_exceptions: - raise ValueError( - "You cannot specify multiple exceptions with `allow_unwrapped=True.`" - " If you want to match one of multiple possible exceptions you should" - " use a `RaisesExc`." - " E.g. `RaisesExc(check=lambda e: isinstance(e, (...)))`", - ) - if allow_unwrapped and isinstance(expected_exception, RaisesGroup): - raise ValueError( - "`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`." - " You might want it in the expected `RaisesGroup`, or" - " `flatten_subgroups=True` if you don't care about the structure.", - ) - if allow_unwrapped and (match is not None or check is not None): - raise ValueError( - "`allow_unwrapped=True` bypasses the `match` and `check` parameters" - " if the exception is unwrapped. If you intended to match/check the" - " exception you should use a `RaisesExc` object. If you want to match/check" - " the exceptiongroup when the exception *is* wrapped you need to" - " do e.g. `if isinstance(exc.value, ExceptionGroup):" - " assert RaisesGroup(...).matches(exc.value)` afterwards.", - ) - - self.expected_exceptions: tuple[ - type[BaseExcT_co] | RaisesExc[BaseExcT_co] | RaisesGroup[BaseException], ... - ] = tuple( - self._parse_excgroup(e, "a BaseException type, RaisesExc, or RaisesGroup") - for e in ( - expected_exception, - *other_exceptions, - ) - ) - - def _parse_excgroup( - self, - exc: ( - type[BaseExcT_co] - | types.GenericAlias - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2] - ), - expected: str, - ) -> type[BaseExcT_co] | RaisesExc[BaseExcT_1] | RaisesGroup[BaseExcT_2]: - # verify exception type and set `self.is_baseexception` - match exc: - case RaisesGroup() if self.flatten_subgroups: - raise ValueError( - "You cannot specify a nested structure inside a RaisesGroup with" - " `flatten_subgroups=True`. The parameter will flatten subgroups" - " in the raised exceptiongroup before matching, which would never" - " match a nested structure.", - ) - case RaisesGroup() | RaisesExc(): - self.is_baseexception |= exc.is_baseexception - exc._nested = True - return exc - case tuple(): - raise TypeError( - f"Expected {expected}, but got {type(exc).__name__!r}.\n" - "RaisesGroup does not support tuples of exception types when expecting one of " - "several possible exception types like RaisesExc.\n" - "If you meant to expect a group with multiple exceptions, list them as separate arguments." - ) - case _: - return super()._parse_exc(exc, expected) - - @overload - def __enter__( - self: RaisesGroup[ExcT_1], - ) -> ExceptionInfo[ExceptionGroup[ExcT_1]]: ... - @overload - def __enter__( - self: RaisesGroup[BaseExcT_1], - ) -> ExceptionInfo[BaseExceptionGroup[BaseExcT_1]]: ... - - def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[BaseException]]: - self.excinfo: ExceptionInfo[BaseExceptionGroup[BaseExcT_co]] = ( - ExceptionInfo.for_later() - ) - return self.excinfo - - def __repr__(self) -> str: - reqs = [ - e.__name__ if isinstance(e, type) else repr(e) - for e in self.expected_exceptions - ] - if self.allow_unwrapped: - reqs.append(f"allow_unwrapped={self.allow_unwrapped}") - if self.flatten_subgroups: - reqs.append(f"flatten_subgroups={self.flatten_subgroups}") - if self.match is not None: - # If no flags were specified, discard the redundant re.compile() here. - reqs.append(f"match={_match_pattern(self.match)!r}") - if self.check is not None: - reqs.append(f"check={repr_callable(self.check)}") - return f"RaisesGroup({', '.join(reqs)})" - - def _unroll_exceptions( - self, - exceptions: Sequence[BaseException], - ) -> Sequence[BaseException]: - """Used if `flatten_subgroups=True`.""" - res: list[BaseException] = [] - for exc in exceptions: - if isinstance(exc, BaseExceptionGroup): - res.extend(self._unroll_exceptions(exc.exceptions)) - - else: - res.append(exc) - return res - - @overload - def matches( - self: RaisesGroup[ExcT_1], - exception: BaseException | None, - ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ... - @overload - def matches( - self: RaisesGroup[BaseExcT_1], - exception: BaseException | None, - ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ... - - def matches( - self, - exception: BaseException | None, - ) -> bool: - """Check if an exception matches the requirements of this RaisesGroup. - If it fails, `RaisesGroup.fail_reason` will be set. - - Example:: - - with pytest.raises(TypeError) as excinfo: - ... - assert RaisesGroup(ValueError).matches(excinfo.value.__cause__) - # the above line is equivalent to - myexc = excinfo.value.__cause - assert isinstance(myexc, BaseExceptionGroup) - assert len(myexc.exceptions) == 1 - assert isinstance(myexc.exceptions[0], ValueError) - """ - self._fail_reason = None - if exception is None: - self._fail_reason = "exception is None" - return False - if not isinstance(exception, BaseExceptionGroup): - # we opt to only print type of the exception here, as the repr would - # likely be quite long - not_group_msg = f"`{type(exception).__name__}()` is not an exception group" - if len(self.expected_exceptions) > 1: - self._fail_reason = not_group_msg - return False - # if we have 1 expected exception, check if it would work even if - # allow_unwrapped is not set - res = self._check_expected(self.expected_exceptions[0], exception) - if res is None and self.allow_unwrapped: - return True - - if res is None: - self._fail_reason = ( - f"{not_group_msg}, but would match with `allow_unwrapped=True`" - ) - elif self.allow_unwrapped: - self._fail_reason = res - else: - self._fail_reason = not_group_msg - return False - - actual_exceptions: Sequence[BaseException] = exception.exceptions - if self.flatten_subgroups: - actual_exceptions = self._unroll_exceptions(actual_exceptions) - - if not self._check_match(exception): - self._fail_reason = cast(str, self._fail_reason) - old_reason = self._fail_reason - if ( - len(actual_exceptions) == len(self.expected_exceptions) == 1 - and isinstance(expected := self.expected_exceptions[0], type) - and isinstance(actual := actual_exceptions[0], expected) - and self._check_match(actual) - ): - assert self.match is not None, "can't be None if _check_match failed" - assert self._fail_reason is old_reason is not None - self._fail_reason += ( - f"\n" - f" but matched the expected `{self._repr_expected(expected)}`.\n" - f" You might want " - f"`RaisesGroup(RaisesExc({expected.__name__}, match={_match_pattern(self.match)!r}))`" - ) - else: - self._fail_reason = old_reason - return False - - # do the full check on expected exceptions - if not self._check_exceptions( - exception, - actual_exceptions, - ): - self._fail_reason = cast(str, self._fail_reason) - assert self._fail_reason is not None - old_reason = self._fail_reason - # if we're not expecting a nested structure, and there is one, do a second - # pass where we try flattening it - if ( - not self.flatten_subgroups - and not any( - isinstance(e, RaisesGroup) for e in self.expected_exceptions - ) - and any(isinstance(e, BaseExceptionGroup) for e in actual_exceptions) - and self._check_exceptions( - exception, - self._unroll_exceptions(exception.exceptions), - ) - ): - # only indent if it's a single-line reason. In a multi-line there's already - # indented lines that this does not belong to. - indent = " " if "\n" not in self._fail_reason else "" - self._fail_reason = ( - old_reason - + f"\n{indent}Did you mean to use `flatten_subgroups=True`?" - ) - else: - self._fail_reason = old_reason - return False - - # Only run `self.check` once we know `exception` is of the correct type. - if not self._check_check(exception): - reason = ( - cast(str, self._fail_reason) + f" on the {type(exception).__name__}" - ) - if ( - len(actual_exceptions) == len(self.expected_exceptions) == 1 - and isinstance(expected := self.expected_exceptions[0], type) - # we explicitly break typing here :) - and self._check_check(actual_exceptions[0]) # type: ignore[arg-type] - ): - self._fail_reason = reason + ( - f", but did return True for the expected {self._repr_expected(expected)}." - f" You might want RaisesGroup(RaisesExc({expected.__name__}, check=<...>))" - ) - else: - self._fail_reason = reason - return False - - return True - - @staticmethod - def _check_expected( - expected_type: ( - type[BaseException] | RaisesExc[BaseException] | RaisesGroup[BaseException] - ), - exception: BaseException, - ) -> str | None: - """Helper method for `RaisesGroup.matches` and `RaisesGroup._check_exceptions` - to check one of potentially several expected exceptions.""" - if isinstance(expected_type, type): - return _check_raw_type(expected_type, exception) - res = expected_type.matches(exception) - if res: - return None - assert expected_type.fail_reason is not None - if expected_type.fail_reason.startswith("\n"): - return f"\n{expected_type!r}: {indent(expected_type.fail_reason, ' ')}" - return f"{expected_type!r}: {expected_type.fail_reason}" - - @staticmethod - def _repr_expected(e: type[BaseException] | AbstractRaises[BaseException]) -> str: - """Get the repr of an expected type/RaisesExc/RaisesGroup, but we only want - the name if it's a type""" - if isinstance(e, type): - return _exception_type_name(e) - return repr(e) - - @overload - def _check_exceptions( - self: RaisesGroup[ExcT_1], - _exception: Exception, - actual_exceptions: Sequence[Exception], - ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ... - @overload - def _check_exceptions( - self: RaisesGroup[BaseExcT_1], - _exception: BaseException, - actual_exceptions: Sequence[BaseException], - ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ... - - def _check_exceptions( - self, - _exception: BaseException, - actual_exceptions: Sequence[BaseException], - ) -> bool: - """Helper method for RaisesGroup.matches that attempts to pair up expected and actual exceptions""" - # The _exception parameter is not used, but necessary for the TypeGuard - - # full table with all results - results = ResultHolder(self.expected_exceptions, actual_exceptions) - - # (indexes of) raised exceptions that haven't (yet) found an expected - remaining_actual = list(range(len(actual_exceptions))) - # (indexes of) expected exceptions that haven't found a matching raised - failed_expected: list[int] = [] - # successful greedy matches - matches: dict[int, int] = {} - - # loop over expected exceptions first to get a more predictable result - for i_exp, expected in enumerate(self.expected_exceptions): - for i_rem in remaining_actual: - res = self._check_expected(expected, actual_exceptions[i_rem]) - results.set_result(i_exp, i_rem, res) - if res is None: - remaining_actual.remove(i_rem) - matches[i_exp] = i_rem - break - else: - failed_expected.append(i_exp) - - # All exceptions matched up successfully - if not remaining_actual and not failed_expected: - return True - - # in case of a single expected and single raised we simplify the output - if 1 == len(actual_exceptions) == len(self.expected_exceptions): - assert not matches - self._fail_reason = res - return False - - # The test case is failing, so we can do a slow and exhaustive check to find - # duplicate matches etc that will be helpful in debugging - for i_exp, expected in enumerate(self.expected_exceptions): - for i_actual, actual in enumerate(actual_exceptions): - if results.has_result(i_exp, i_actual): - continue - results.set_result( - i_exp, i_actual, self._check_expected(expected, actual) - ) - - successful_str = ( - f"{len(matches)} matched exception{'s' if len(matches) > 1 else ''}. " - if matches - else "" - ) - - # all expected were found - if not failed_expected and results.no_match_for_actual(remaining_actual): - self._fail_reason = ( - f"{successful_str}Unexpected exception(s):" - f" {[actual_exceptions[i] for i in remaining_actual]!r}" - ) - return False - # all raised exceptions were expected - if not remaining_actual and results.no_match_for_expected(failed_expected): - no_match_for_str = ", ".join( - self._repr_expected(self.expected_exceptions[i]) - for i in failed_expected - ) - self._fail_reason = f"{successful_str}Too few exceptions raised, found no match for: [{no_match_for_str}]" - return False - - # if there's only one remaining and one failed, and the unmatched didn't match anything else, - # we elect to only print why the remaining and the failed didn't match. - if ( - 1 == len(remaining_actual) == len(failed_expected) - and results.no_match_for_actual(remaining_actual) - and results.no_match_for_expected(failed_expected) - ): - self._fail_reason = f"{successful_str}{results.get_result(failed_expected[0], remaining_actual[0])}" - return False - - # there's both expected and raised exceptions without matches - s = "" - if matches: - s += f"\n{successful_str}" - indent_1 = " " * 2 - indent_2 = " " * 4 - - if not remaining_actual: - s += "\nToo few exceptions raised!" - elif not failed_expected: - s += "\nUnexpected exception(s)!" - - if failed_expected: - s += "\nThe following expected exceptions did not find a match:" - rev_matches = {v: k for k, v in matches.items()} - for i_failed in failed_expected: - s += ( - f"\n{indent_1}{self._repr_expected(self.expected_exceptions[i_failed])}" - ) - for i_actual, actual in enumerate(actual_exceptions): - if results.get_result(i_failed, i_actual) is None: - # we print full repr of match target - s += ( - f"\n{indent_2}It matches {backquote(repr(actual))} which was paired with " - + backquote( - self._repr_expected( - self.expected_exceptions[rev_matches[i_actual]] - ) - ) - ) - - if remaining_actual: - s += "\nThe following raised exceptions did not find a match" - for i_actual in remaining_actual: - s += f"\n{indent_1}{actual_exceptions[i_actual]!r}:" - for i_exp, expected in enumerate(self.expected_exceptions): - res = results.get_result(i_exp, i_actual) - if i_exp in failed_expected: - assert res is not None - if res[0] != "\n": - s += "\n" - s += indent(res, indent_2) - if res is None: - # we print full repr of match target - s += ( - f"\n{indent_2}It matches {backquote(self._repr_expected(expected))} " - f"which was paired with {backquote(repr(actual_exceptions[matches[i_exp]]))}" - ) - - if len(self.expected_exceptions) == len(actual_exceptions) and possible_match( - results - ): - s += ( - "\nThere exist a possible match when attempting an exhaustive check," - " but RaisesGroup uses a greedy algorithm. " - "Please make your expected exceptions more stringent with `RaisesExc` etc" - " so the greedy algorithm can function." - ) - self._fail_reason = s - return False - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> bool: - __tracebackhide__ = True - if exc_type is None: - fail(f"DID NOT RAISE any exception, expected `{self.expected_type()}`") - - assert self.excinfo is not None, ( - "Internal error - should have been constructed in __enter__" - ) - - # group_str is the only thing that differs between RaisesExc and RaisesGroup... - # I might just scrap it? Or make it part of fail_reason - group_str = ( - "(group)" - if self.allow_unwrapped and not issubclass(exc_type, BaseExceptionGroup) - else "group" - ) - - if not self.matches(exc_val): - fail(f"Raised exception {group_str} did not match: {self._fail_reason}") - - # Cast to narrow the exception type now that it's verified.... - # even though the TypeGuard in self.matches should be narrowing - exc_info = cast( - "tuple[type[BaseExceptionGroup[BaseExcT_co]], BaseExceptionGroup[BaseExcT_co], types.TracebackType]", - (exc_type, exc_val, exc_tb), - ) - self.excinfo.fill_unfilled(exc_info) - return True - - def expected_type(self) -> str: - subexcs = [] - for e in self.expected_exceptions: - if isinstance(e, RaisesExc): - subexcs.append(repr(e)) - elif isinstance(e, RaisesGroup): - subexcs.append(e.expected_type()) - elif isinstance(e, type): - subexcs.append(e.__name__) - else: # pragma: no cover - raise AssertionError("unknown type") - group_type = "Base" if self.is_baseexception else "" - return f"{group_type}ExceptionGroup({', '.join(subexcs)})" - - -@final -class NotChecked: - """Singleton for unchecked values in ResultHolder""" - - -class ResultHolder: - """Container for results of checking exceptions. - Used in RaisesGroup._check_exceptions and possible_match. - """ - - def __init__( - self, - expected_exceptions: tuple[ - type[BaseException] | AbstractRaises[BaseException], ... - ], - actual_exceptions: Sequence[BaseException], - ) -> None: - self.results: list[list[str | type[NotChecked] | None]] = [ - [NotChecked for _ in expected_exceptions] for _ in actual_exceptions - ] - - def set_result(self, expected: int, actual: int, result: str | None) -> None: - self.results[actual][expected] = result - - def get_result(self, expected: int, actual: int) -> str | None: - res = self.results[actual][expected] - assert res is not NotChecked - return res - - def has_result(self, expected: int, actual: int) -> bool: - return self.results[actual][expected] is not NotChecked - - def no_match_for_expected(self, expected: list[int]) -> bool: - for i in expected: - for actual_results in self.results: - assert actual_results[i] is not NotChecked - if actual_results[i] is None: - return False - return True - - def no_match_for_actual(self, actual: list[int]) -> bool: - for i in actual: - for res in self.results[i]: - assert res is not NotChecked - if res is None: - return False - return True - - -def possible_match(results: ResultHolder, used: set[int] | None = None) -> bool: - if used is None: - used = set() - curr_row = len(used) - if curr_row == len(results.results): - return True - return any( - val is None and i not in used and possible_match(results, used | {i}) - for (i, val) in enumerate(results.results[curr_row]) - ) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/recwarn.py b/tests/venv2/lib/python3.11/site-packages/_pytest/recwarn.py deleted file mode 100644 index c3cb10b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/recwarn.py +++ /dev/null @@ -1,379 +0,0 @@ -# mypy: allow-untyped-defs -"""Record warnings during test function execution.""" - -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterator -from pprint import pformat -import re -from types import TracebackType -from typing import Any -from typing import final -from typing import overload -from typing import TYPE_CHECKING -from typing import TypeVar - - -if TYPE_CHECKING: - from typing_extensions import ParamSpec - from typing_extensions import Self - - P = ParamSpec("P") - -import warnings - -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.outcomes import Exit -from _pytest.outcomes import fail - - -T = TypeVar("T") - - -@fixture -def recwarn() -> Generator[WarningsRecorder]: - """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - - See :ref:`warnings` for information on warning categories. - """ - wrec = WarningsRecorder(_ispytest=True) - with wrec: - warnings.simplefilter("default") - yield wrec - - -@overload -def deprecated_call( - *, match: str | re.Pattern[str] | None = ... -) -> WarningsRecorder: ... - - -@overload -def deprecated_call(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T: ... - - -def deprecated_call( - func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any -) -> WarningsRecorder | Any: - """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. - - This function can be used as a context manager:: - - >>> import warnings - >>> def api_call_v2(): - ... warnings.warn('use v3 of this api', DeprecationWarning) - ... return 200 - - >>> import pytest - >>> with pytest.deprecated_call(): - ... assert api_call_v2() == 200 - >>> with pytest.deprecated_call(match="^use v3 of this api$") as warning_messages: - ... assert api_call_v2() == 200 - - You may use the keyword argument ``match`` to assert - that the warning matches a text or regex. - - The return value is a list of :class:`warnings.WarningMessage` objects, - one for each warning emitted - (regardless of whether it is an ``expected_warning`` or not). - """ - __tracebackhide__ = True - dep_warnings = (DeprecationWarning, PendingDeprecationWarning, FutureWarning) - if func is None: - return warns(dep_warnings, *args, **kwargs) - - with warns(dep_warnings): - return func(*args, **kwargs) - - -@overload -def warns( - expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., - *, - match: str | re.Pattern[str] | None = ..., -) -> WarningsChecker: ... - - -@overload -def warns( - expected_warning: type[Warning] | tuple[type[Warning], ...], - func: Callable[P, T], - *args: P.args, - **kwargs: P.kwargs, -) -> T: ... - - -def warns( - expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, - func: Callable[..., object] | None = None, - *args: Any, - **kwargs: Any, -) -> WarningsChecker | Any: - r"""Assert that code raises a particular class of warning. - - Specifically, the parameter ``expected_warning`` can be a warning class or tuple - of warning classes, and the code inside the ``with`` block must issue at least one - warning of that class or classes. - - This helper produces a list of :class:`warnings.WarningMessage` objects, one for - each warning emitted (regardless of whether it is an ``expected_warning`` or not). - Since pytest 8.0, unmatched warnings are also re-emitted when the context closes. - - This function should be used as a context manager:: - - >>> import pytest - >>> with pytest.warns(RuntimeWarning): - ... warnings.warn("my warning", RuntimeWarning) - - The ``match`` keyword argument can be used to assert - that the warning matches a text or regex:: - - >>> with pytest.warns(UserWarning, match='must be 0 or None'): - ... warnings.warn("value must be 0 or None", UserWarning) - - >>> with pytest.warns(UserWarning, match=r'must be \d+$'): - ... warnings.warn("value must be 42", UserWarning) - - >>> with pytest.warns(UserWarning): # catch re-emitted warning - ... with pytest.warns(UserWarning, match=r'must be \d+$'): - ... warnings.warn("this is not here", UserWarning) - Traceback (most recent call last): - ... - Failed: Regex pattern did not match any of the 1 warnings emitted. - Regex: ... - Emitted warnings: ...UserWarning... - - **Using with** ``pytest.mark.parametrize`` - - When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests - such that some runs raise a warning and others do not. - - This could be achieved in the same way as with exceptions, see - :ref:`parametrizing_conditional_raising` for an example. - - """ - __tracebackhide__ = True - if func is None and not args: - match: str | re.Pattern[str] | None = kwargs.pop("match", None) - if kwargs: - argnames = ", ".join(sorted(kwargs)) - raise TypeError( - f"Unexpected keyword arguments passed to pytest.warns: {argnames}" - "\nUse context-manager form instead?" - ) - return WarningsChecker(expected_warning, match_expr=match, _ispytest=True) - else: - if not callable(func): - raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") - with WarningsChecker(expected_warning, _ispytest=True): - return func(*args, **kwargs) - - -class WarningsRecorder(warnings.catch_warnings): - """A context manager to record raised warnings. - - Each recorded warning is an instance of :class:`warnings.WarningMessage`. - - Adapted from `warnings.catch_warnings`. - - .. note:: - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated - differently; see :ref:`ensuring_function_triggers`. - - """ - - def __init__(self, *, _ispytest: bool = False) -> None: - check_ispytest(_ispytest) - super().__init__(record=True) - self._entered = False - self._list: list[warnings.WarningMessage] = [] - - @property - def list(self) -> list[warnings.WarningMessage]: - """The list of recorded warnings.""" - return self._list - - def __getitem__(self, i: int) -> warnings.WarningMessage: - """Get a recorded warning by index.""" - return self._list[i] - - def __iter__(self) -> Iterator[warnings.WarningMessage]: - """Iterate through the recorded warnings.""" - return iter(self._list) - - def __len__(self) -> int: - """The number of recorded warnings.""" - return len(self._list) - - def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: - """Pop the first recorded warning which is an instance of ``cls``, - but not an instance of a child class of any other match. - Raises ``AssertionError`` if there is no match. - """ - best_idx: int | None = None - for i, w in enumerate(self._list): - if w.category == cls: - return self._list.pop(i) # exact match, stop looking - if issubclass(w.category, cls) and ( - best_idx is None - or not issubclass(w.category, self._list[best_idx].category) - ): - best_idx = i - if best_idx is not None: - return self._list.pop(best_idx) - __tracebackhide__ = True - raise AssertionError(f"{cls!r} not found in warning list") - - def clear(self) -> None: - """Clear the list of recorded warnings.""" - self._list[:] = [] - - # Type ignored because we basically want the `catch_warnings` generic type - # parameter to be ourselves but that is not possible(?). - def __enter__(self) -> Self: # type: ignore[override] - if self._entered: - __tracebackhide__ = True - raise RuntimeError(f"Cannot enter {self!r} twice") - _list = super().__enter__() - # record=True means it's None. - assert _list is not None - self._list = _list - warnings.simplefilter("always") - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - if not self._entered: - __tracebackhide__ = True - raise RuntimeError(f"Cannot exit {self!r} without entering first") - - super().__exit__(exc_type, exc_val, exc_tb) - - # Built-in catch_warnings does not reset entered state so we do it - # manually here for this context manager to become reusable. - self._entered = False - - -@final -class WarningsChecker(WarningsRecorder): - def __init__( - self, - expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, - match_expr: str | re.Pattern[str] | None = None, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - super().__init__(_ispytest=True) - - msg = "exceptions must be derived from Warning, not %s" - if isinstance(expected_warning, tuple): - for exc in expected_warning: - if not issubclass(exc, Warning): - raise TypeError(msg % type(exc)) - expected_warning_tup = expected_warning - elif isinstance(expected_warning, type) and issubclass( - expected_warning, Warning - ): - expected_warning_tup = (expected_warning,) - else: - raise TypeError(msg % type(expected_warning)) - - self.expected_warning = expected_warning_tup - self.match_expr = match_expr - - def matches(self, warning: warnings.WarningMessage) -> bool: - assert self.expected_warning is not None - return issubclass(warning.category, self.expected_warning) and bool( - self.match_expr is None or re.search(self.match_expr, str(warning.message)) - ) - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - super().__exit__(exc_type, exc_val, exc_tb) - - __tracebackhide__ = True - - # BaseExceptions like pytest.{skip,fail,xfail,exit} or Ctrl-C within - # pytest.warns should *not* trigger "DID NOT WARN" and get suppressed - # when the warning doesn't happen. Control-flow exceptions should always - # propagate. - if exc_val is not None and ( - not isinstance(exc_val, Exception) - # Exit is an Exception, not a BaseException, for some reason. - or isinstance(exc_val, Exit) - ): - return - - def found_str() -> str: - return pformat([record.message for record in self], indent=2) - - try: - if not any(issubclass(w.category, self.expected_warning) for w in self): - fail( - f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" - f" Emitted warnings: {found_str()}." - ) - elif not any(self.matches(w) for w in self): - escape_hint = "" - if isinstance(self.match_expr, str) and any( - self.match_expr == str(w.message) - for w in self - if issubclass(w.category, self.expected_warning) - ): - escape_hint = "\n Did you mean to `re.escape()` the regex?" - fail( - f"Regex pattern did not match any of the {len(self)} warnings emitted.\n" - f" Regex: {self.match_expr!r}\n" - f" Emitted warnings: {found_str()}.{escape_hint}" - ) - finally: - # Whether or not any warnings matched, we want to re-emit all unmatched warnings. - for w in self: - if not self.matches(w): - warnings.warn_explicit( - message=w.message, - category=w.category, - filename=w.filename, - lineno=w.lineno, - module=w.__module__, - source=w.source, - ) - - # Currently in Python it is possible to pass other types than an - # `str` message when creating `Warning` instances, however this - # causes an exception when :func:`warnings.filterwarnings` is used - # to filter those warnings. See - # https://github.com/python/cpython/issues/103577 for a discussion. - # While this can be considered a bug in CPython, we put guards in - # pytest as the error message produced without this check in place - # is confusing (#10865). - for w in self: - if type(w.message) is not UserWarning: - # If the warning was of an incorrect type then `warnings.warn()` - # creates a UserWarning. Any other warning must have been specified - # explicitly. - continue - if not w.message.args: - # UserWarning() without arguments must have been specified explicitly. - continue - msg = w.message.args[0] - if isinstance(msg, str): - continue - # It's possible that UserWarning was explicitly specified, and - # its first argument was not a string. But that case can't be - # distinguished from an invalid type. - raise TypeError( - f"Warning must be str or Warning, got {msg!r} (type {type(msg).__name__})" - ) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/reports.py b/tests/venv2/lib/python3.11/site-packages/_pytest/reports.py deleted file mode 100644 index 011a69d..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/reports.py +++ /dev/null @@ -1,694 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence -import dataclasses -from io import StringIO -import os -from pprint import pprint -import sys -from typing import Any -from typing import cast -from typing import final -from typing import Literal -from typing import NoReturn -from typing import TYPE_CHECKING - -from _pytest._code.code import ExceptionChainRepr -from _pytest._code.code import ExceptionInfo -from _pytest._code.code import ExceptionRepr -from _pytest._code.code import ReprEntry -from _pytest._code.code import ReprEntryNative -from _pytest._code.code import ReprExceptionInfo -from _pytest._code.code import ReprFileLocation -from _pytest._code.code import ReprFuncArgs -from _pytest._code.code import ReprLocals -from _pytest._code.code import ReprTraceback -from _pytest._code.code import TerminalRepr -from _pytest._io import TerminalWriter -from _pytest.config import Config -from _pytest.nodes import Collector -from _pytest.nodes import Item -from _pytest.outcomes import fail -from _pytest.outcomes import skip - - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - - -if TYPE_CHECKING: - from typing_extensions import Self - - from _pytest.runner import CallInfo - - -def getworkerinfoline(node): - try: - return node._workerinfocache - except AttributeError: - d = node.workerinfo - ver = "{}.{}.{}".format(*d["version_info"][:3]) - node._workerinfocache = s = "[{}] {} -- Python {} {}".format( - d["id"], d["sysplatform"], ver, d["executable"] - ) - return s - - -class BaseReport: - when: str | None - location: tuple[str, int | None, str] | None - longrepr: ( - None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr - ) - sections: list[tuple[str, str]] - nodeid: str - outcome: Literal["passed", "failed", "skipped"] - - def __init__(self, **kw: Any) -> None: - self.__dict__.update(kw) - - if TYPE_CHECKING: - # Can have arbitrary fields given to __init__(). - def __getattr__(self, key: str) -> Any: ... - - def toterminal(self, out: TerminalWriter) -> None: - if hasattr(self, "node"): - worker_info = getworkerinfoline(self.node) - if worker_info: - out.line(worker_info) - - longrepr = self.longrepr - if longrepr is None: - return - - if hasattr(longrepr, "toterminal"): - longrepr_terminal = cast(TerminalRepr, longrepr) - longrepr_terminal.toterminal(out) - else: - try: - s = str(longrepr) - except UnicodeEncodeError: - s = "" - out.line(s) - - def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: - for name, content in self.sections: - if name.startswith(prefix): - yield prefix, content - - @property - def longreprtext(self) -> str: - """Read-only property that returns the full string representation of - ``longrepr``. - - .. versionadded:: 3.0 - """ - file = StringIO() - tw = TerminalWriter(file) - tw.hasmarkup = False - self.toterminal(tw) - exc = file.getvalue() - return exc.strip() - - @property - def caplog(self) -> str: - """Return captured log lines, if log capturing is enabled. - - .. versionadded:: 3.5 - """ - return "\n".join( - content for (prefix, content) in self.get_sections("Captured log") - ) - - @property - def capstdout(self) -> str: - """Return captured text from stdout, if capturing is enabled. - - .. versionadded:: 3.0 - """ - return "".join( - content for (prefix, content) in self.get_sections("Captured stdout") - ) - - @property - def capstderr(self) -> str: - """Return captured text from stderr, if capturing is enabled. - - .. versionadded:: 3.0 - """ - return "".join( - content for (prefix, content) in self.get_sections("Captured stderr") - ) - - @property - def passed(self) -> bool: - """Whether the outcome is passed.""" - return self.outcome == "passed" - - @property - def failed(self) -> bool: - """Whether the outcome is failed.""" - return self.outcome == "failed" - - @property - def skipped(self) -> bool: - """Whether the outcome is skipped.""" - return self.outcome == "skipped" - - @property - def fspath(self) -> str: - """The path portion of the reported node, as a string.""" - return self.nodeid.split("::")[0] - - @property - def count_towards_summary(self) -> bool: - """**Experimental** Whether this report should be counted towards the - totals shown at the end of the test session: "1 passed, 1 failure, etc". - - .. note:: - - This function is considered **experimental**, so beware that it is subject to changes - even in patch releases. - """ - return True - - @property - def head_line(self) -> str | None: - """**Experimental** The head line shown with longrepr output for this - report, more commonly during traceback representation during - failures:: - - ________ Test.foo ________ - - - In the example above, the head_line is "Test.foo". - - .. note:: - - This function is considered **experimental**, so beware that it is subject to changes - even in patch releases. - """ - if self.location is not None: - _fspath, _lineno, domain = self.location - return domain - return None - - def _get_verbose_word_with_markup( - self, config: Config, default_markup: Mapping[str, bool] - ) -> tuple[str, Mapping[str, bool]]: - _category, _short, verbose = config.hook.pytest_report_teststatus( - report=self, config=config - ) - - if isinstance(verbose, str): - return verbose, default_markup - - if isinstance(verbose, Sequence) and len(verbose) == 2: - word, markup = verbose - if isinstance(word, str) and isinstance(markup, Mapping): - return word, markup - - fail( # pragma: no cover - "pytest_report_teststatus() hook (from a plugin) returned " - f"an invalid verbose value: {verbose!r}.\nExpected either a string " - "or a tuple of (word, markup)." - ) - - def _to_json(self) -> dict[str, Any]: - """Return the contents of this report as a dict of builtin entries, - suitable for serialization. - - This was originally the serialize_report() function from xdist (ca03269). - - Experimental method. - """ - return _report_to_json(self) - - @classmethod - def _from_json(cls, reportdict: dict[str, object]) -> Self: - """Create either a TestReport or CollectReport, depending on the calling class. - - It is the callers responsibility to know which class to pass here. - - This was originally the serialize_report() function from xdist (ca03269). - - Experimental method. - """ - kwargs = _report_kwargs_from_json(reportdict) - return cls(**kwargs) - - -def _report_unserialization_failure( - type_name: str, report_class: type[BaseReport], reportdict -) -> NoReturn: - url = "https://github.com/pytest-dev/pytest/issues" - stream = StringIO() - pprint("-" * 100, stream=stream) - pprint(f"INTERNALERROR: Unknown entry type returned: {type_name}", stream=stream) - pprint(f"report_name: {report_class}", stream=stream) - pprint(reportdict, stream=stream) - pprint(f"Please report this bug at {url}", stream=stream) - pprint("-" * 100, stream=stream) - raise RuntimeError(stream.getvalue()) - - -def _format_failed_longrepr( - item: Item, call: CallInfo[None], excinfo: ExceptionInfo[BaseException] -): - if call.when == "call": - longrepr = item.repr_failure(excinfo) - else: - # Exception in setup or teardown. - longrepr = item._repr_failure_py( - excinfo, style=item.config.getoption("tbstyle", "auto") - ) - return longrepr - - -def _format_exception_group_all_skipped_longrepr( - item: Item, - excinfo: ExceptionInfo[BaseExceptionGroup[BaseException | BaseExceptionGroup]], -) -> tuple[str, int, str]: - r = excinfo._getreprcrash() - assert r is not None, ( - "There should always be a traceback entry for skipping a test." - ) - if all( - getattr(skip, "_use_item_location", False) for skip in excinfo.value.exceptions - ): - path, line = item.reportinfo()[:2] - assert line is not None - loc = (os.fspath(path), line + 1) - default_msg = "skipped" - else: - loc = (str(r.path), r.lineno) - default_msg = r.message - - # Get all unique skip messages. - msgs: list[str] = [] - for exception in excinfo.value.exceptions: - m = getattr(exception, "msg", None) or ( - exception.args[0] if exception.args else None - ) - if m and m not in msgs: - msgs.append(m) - - reason = "; ".join(msgs) if msgs else default_msg - longrepr = (*loc, reason) - return longrepr - - -class TestReport(BaseReport): - """Basic test report object (also used for setup and teardown calls if - they fail). - - Reports can contain arbitrary extra attributes. - """ - - __test__ = False - - # Defined by skipping plugin. - # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish. - wasxfail: str - - def __init__( - self, - nodeid: str, - location: tuple[str, int | None, str], - keywords: Mapping[str, Any], - outcome: Literal["passed", "failed", "skipped"], - longrepr: None - | ExceptionInfo[BaseException] - | tuple[str, int, str] - | str - | TerminalRepr, - when: Literal["setup", "call", "teardown"], - sections: Iterable[tuple[str, str]] = (), - duration: float = 0, - start: float = 0, - stop: float = 0, - user_properties: Iterable[tuple[str, object]] | None = None, - **extra, - ) -> None: - #: Normalized collection nodeid. - self.nodeid = nodeid - - #: A (filesystempath, lineno, domaininfo) tuple indicating the - #: actual location of a test item - it might be different from the - #: collected one e.g. if a method is inherited from a different module. - #: The filesystempath may be relative to ``config.rootdir``. - #: The line number is 0-based. - self.location: tuple[str, int | None, str] = location - - #: A name -> value dictionary containing all keywords and - #: markers associated with a test invocation. - self.keywords: Mapping[str, Any] = keywords - - #: Test outcome, always one of "passed", "failed", "skipped". - self.outcome = outcome - - #: None or a failure representation. - self.longrepr = longrepr - - #: One of 'setup', 'call', 'teardown' to indicate runtest phase. - self.when: Literal["setup", "call", "teardown"] = when - - #: User properties is a list of tuples (name, value) that holds user - #: defined properties of the test. - self.user_properties = list(user_properties or []) - - #: Tuples of str ``(heading, content)`` with extra information - #: for the test report. Used by pytest to add text captured - #: from ``stdout``, ``stderr``, and intercepted logging events. May - #: be used by other plugins to add arbitrary information to reports. - self.sections = list(sections) - - #: Time it took to run just the test. - self.duration: float = duration - - #: The system time when the call started, in seconds since the epoch. - self.start: float = start - #: The system time when the call ended, in seconds since the epoch. - self.stop: float = stop - - self.__dict__.update(extra) - - def __repr__(self) -> str: - return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" - - @classmethod - def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: - """Create and fill a TestReport with standard item and call info. - - :param item: The item. - :param call: The call info. - """ - when = call.when - # Remove "collect" from the Literal type -- only for collection calls. - assert when != "collect" - duration = call.duration - start = call.start - stop = call.stop - keywords = {x: 1 for x in item.keywords} - excinfo = call.excinfo - sections = [] - if not call.excinfo: - outcome: Literal["passed", "failed", "skipped"] = "passed" - longrepr: ( - None - | ExceptionInfo[BaseException] - | tuple[str, int, str] - | str - | TerminalRepr - ) = None - else: - if not isinstance(excinfo, ExceptionInfo): - outcome = "failed" - longrepr = excinfo - elif isinstance(excinfo.value, skip.Exception): - outcome = "skipped" - r = excinfo._getreprcrash() - assert r is not None, ( - "There should always be a traceback entry for skipping a test." - ) - if excinfo.value._use_item_location: - path, line = item.reportinfo()[:2] - assert line is not None - longrepr = (os.fspath(path), line + 1, r.message) - else: - longrepr = (str(r.path), r.lineno, r.message) - elif isinstance(excinfo.value, BaseExceptionGroup) and ( - excinfo.value.split(skip.Exception)[1] is None - ): - # All exceptions in the group are skip exceptions. - outcome = "skipped" - excinfo = cast( - ExceptionInfo[ - BaseExceptionGroup[BaseException | BaseExceptionGroup] - ], - excinfo, - ) - longrepr = _format_exception_group_all_skipped_longrepr(item, excinfo) - else: - outcome = "failed" - longrepr = _format_failed_longrepr(item, call, excinfo) - for rwhen, key, content in item._report_sections: - sections.append((f"Captured {key} {rwhen}", content)) - return cls( - item.nodeid, - item.location, - keywords, - outcome, - longrepr, - when, - sections, - duration, - start, - stop, - user_properties=item.user_properties, - ) - - -@final -class CollectReport(BaseReport): - """Collection report object. - - Reports can contain arbitrary extra attributes. - """ - - when = "collect" - - def __init__( - self, - nodeid: str, - outcome: Literal["passed", "failed", "skipped"], - longrepr: None - | ExceptionInfo[BaseException] - | tuple[str, int, str] - | str - | TerminalRepr, - result: list[Item | Collector] | None, - sections: Iterable[tuple[str, str]] = (), - **extra, - ) -> None: - #: Normalized collection nodeid. - self.nodeid = nodeid - - #: Test outcome, always one of "passed", "failed", "skipped". - self.outcome = outcome - - #: None or a failure representation. - self.longrepr = longrepr - - #: The collected items and collection nodes. - self.result = result or [] - - #: Tuples of str ``(heading, content)`` with extra information - #: for the test report. Used by pytest to add text captured - #: from ``stdout``, ``stderr``, and intercepted logging events. May - #: be used by other plugins to add arbitrary information to reports. - self.sections = list(sections) - - self.__dict__.update(extra) - - @property - def location( # type:ignore[override] - self, - ) -> tuple[str, int | None, str] | None: - return (self.fspath, None, self.fspath) - - def __repr__(self) -> str: - return f"" - - -class CollectErrorRepr(TerminalRepr): - def __init__(self, msg: str) -> None: - self.longrepr = msg - - def toterminal(self, out: TerminalWriter) -> None: - out.line(self.longrepr, red=True) - - -def pytest_report_to_serializable( - report: CollectReport | TestReport, -) -> dict[str, Any] | None: - if isinstance(report, TestReport | CollectReport): - data = report._to_json() - data["$report_type"] = report.__class__.__name__ - return data - # TODO: Check if this is actually reachable. - return None # type: ignore[unreachable] - - -def pytest_report_from_serializable( - data: dict[str, Any], -) -> CollectReport | TestReport | None: - if "$report_type" in data: - if data["$report_type"] == "TestReport": - return TestReport._from_json(data) - elif data["$report_type"] == "CollectReport": - return CollectReport._from_json(data) - assert False, "Unknown report_type unserialize data: {}".format( - data["$report_type"] - ) - return None - - -def _report_to_json(report: BaseReport) -> dict[str, Any]: - """Return the contents of this report as a dict of builtin entries, - suitable for serialization. - - This was originally the serialize_report() function from xdist (ca03269). - """ - - def serialize_repr_entry( - entry: ReprEntry | ReprEntryNative, - ) -> dict[str, Any]: - data = dataclasses.asdict(entry) - for key, value in data.items(): - if hasattr(value, "__dict__"): - data[key] = dataclasses.asdict(value) - entry_data = {"type": type(entry).__name__, "data": data} - return entry_data - - def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: - result = dataclasses.asdict(reprtraceback) - result["reprentries"] = [ - serialize_repr_entry(x) for x in reprtraceback.reprentries - ] - return result - - def serialize_repr_crash( - reprcrash: ReprFileLocation | None, - ) -> dict[str, Any] | None: - if reprcrash is not None: - return dataclasses.asdict(reprcrash) - else: - return None - - def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]: - assert rep.longrepr is not None - # TODO: Investigate whether the duck typing is really necessary here. - longrepr = cast(ExceptionRepr, rep.longrepr) - result: dict[str, Any] = { - "reprcrash": serialize_repr_crash(longrepr.reprcrash), - "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), - "sections": longrepr.sections, - } - if isinstance(longrepr, ExceptionChainRepr): - result["chain"] = [] - for repr_traceback, repr_crash, description in longrepr.chain: - result["chain"].append( - ( - serialize_repr_traceback(repr_traceback), - serialize_repr_crash(repr_crash), - description, - ) - ) - else: - result["chain"] = None - return result - - d = report.__dict__.copy() - if hasattr(report.longrepr, "toterminal"): - if hasattr(report.longrepr, "reprtraceback") and hasattr( - report.longrepr, "reprcrash" - ): - d["longrepr"] = serialize_exception_longrepr(report) - else: - d["longrepr"] = str(report.longrepr) - else: - d["longrepr"] = report.longrepr - for name in d: - if isinstance(d[name], os.PathLike): - d[name] = os.fspath(d[name]) - elif name == "result": - d[name] = None # for now - return d - - -def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: - """Return **kwargs that can be used to construct a TestReport or - CollectReport instance. - - This was originally the serialize_report() function from xdist (ca03269). - """ - - def deserialize_repr_entry(entry_data): - data = entry_data["data"] - entry_type = entry_data["type"] - if entry_type == "ReprEntry": - reprfuncargs = None - reprfileloc = None - reprlocals = None - if data["reprfuncargs"]: - reprfuncargs = ReprFuncArgs(**data["reprfuncargs"]) - if data["reprfileloc"]: - reprfileloc = ReprFileLocation(**data["reprfileloc"]) - if data["reprlocals"]: - reprlocals = ReprLocals(data["reprlocals"]["lines"]) - - reprentry: ReprEntry | ReprEntryNative = ReprEntry( - lines=data["lines"], - reprfuncargs=reprfuncargs, - reprlocals=reprlocals, - reprfileloc=reprfileloc, - style=data["style"], - ) - elif entry_type == "ReprEntryNative": - reprentry = ReprEntryNative(data["lines"]) - else: - _report_unserialization_failure(entry_type, TestReport, reportdict) - return reprentry - - def deserialize_repr_traceback(repr_traceback_dict): - repr_traceback_dict["reprentries"] = [ - deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"] - ] - return ReprTraceback(**repr_traceback_dict) - - def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): - if repr_crash_dict is not None: - return ReprFileLocation(**repr_crash_dict) - else: - return None - - if ( - reportdict["longrepr"] - and "reprcrash" in reportdict["longrepr"] - and "reprtraceback" in reportdict["longrepr"] - ): - reprtraceback = deserialize_repr_traceback( - reportdict["longrepr"]["reprtraceback"] - ) - reprcrash = deserialize_repr_crash(reportdict["longrepr"]["reprcrash"]) - if reportdict["longrepr"]["chain"]: - chain = [] - for repr_traceback_data, repr_crash_data, description in reportdict[ - "longrepr" - ]["chain"]: - chain.append( - ( - deserialize_repr_traceback(repr_traceback_data), - deserialize_repr_crash(repr_crash_data), - description, - ) - ) - exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr( - chain - ) - else: - exception_info = ReprExceptionInfo( - reprtraceback=reprtraceback, - reprcrash=reprcrash, - ) - - for section in reportdict["longrepr"]["sections"]: - exception_info.addsection(*section) - reportdict["longrepr"] = exception_info - - return reportdict diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/runner.py b/tests/venv2/lib/python3.11/site-packages/_pytest/runner.py deleted file mode 100644 index 3f03cfa..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/runner.py +++ /dev/null @@ -1,593 +0,0 @@ -# mypy: allow-untyped-defs -"""Basic collect and runtest protocol implementations.""" - -from __future__ import annotations - -import bdb -from collections.abc import Callable -import dataclasses -import os -import sys -import types -from typing import cast -from typing import final -from typing import Generic -from typing import Literal -from typing import TYPE_CHECKING -from typing import TypeVar - -from .config import Config -from .reports import BaseReport -from .reports import CollectErrorRepr -from .reports import CollectReport -from .reports import TestReport -from _pytest import timing -from _pytest._code.code import ExceptionChainRepr -from _pytest._code.code import ExceptionInfo -from _pytest._code.code import TerminalRepr -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.nodes import Collector -from _pytest.nodes import Directory -from _pytest.nodes import Item -from _pytest.nodes import Node -from _pytest.outcomes import Exit -from _pytest.outcomes import OutcomeException -from _pytest.outcomes import Skipped -from _pytest.outcomes import TEST_OUTCOME - - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - -if TYPE_CHECKING: - from _pytest.main import Session - from _pytest.terminal import TerminalReporter - -# -# pytest plugin hooks. - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "Reporting", after="general") - group.addoption( - "--durations", - action="store", - type=int, - default=None, - metavar="N", - help="Show N slowest setup/test durations (N=0 for all)", - ) - group.addoption( - "--durations-min", - action="store", - type=float, - default=None, - metavar="N", - help="Minimal duration in seconds for inclusion in slowest list. " - "Default: 0.005 (or 0.0 if -vv is given).", - ) - - -def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: - durations = terminalreporter.config.option.durations - durations_min = terminalreporter.config.option.durations_min - verbose = terminalreporter.config.get_verbosity() - if durations is None: - return - if durations_min is None: - durations_min = 0.005 if verbose < 2 else 0.0 - tr = terminalreporter - dlist = [] - for replist in tr.stats.values(): - for rep in replist: - if hasattr(rep, "duration"): - dlist.append(rep) - if not dlist: - return - dlist.sort(key=lambda x: x.duration, reverse=True) - if not durations: - tr.write_sep("=", "slowest durations") - else: - tr.write_sep("=", f"slowest {durations} durations") - dlist = dlist[:durations] - - for i, rep in enumerate(dlist): - if rep.duration < durations_min: - tr.write_line("") - message = f"({len(dlist) - i} durations < {durations_min:g}s hidden." - if terminalreporter.config.option.durations_min is None: - message += " Use -vv to show these durations." - message += ")" - tr.write_line(message) - break - tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") - - -def pytest_sessionstart(session: Session) -> None: - session._setupstate = SetupState() - - -def pytest_sessionfinish(session: Session) -> None: - session._setupstate.teardown_exact(None) - - -def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: - ihook = item.ihook - ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) - runtestprotocol(item, nextitem=nextitem) - ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location) - return True - - -def runtestprotocol( - item: Item, log: bool = True, nextitem: Item | None = None -) -> list[TestReport]: - hasrequest = hasattr(item, "_request") - if hasrequest and not item._request: # type: ignore[attr-defined] - # This only happens if the item is re-run, as is done by - # pytest-rerunfailures. - item._initrequest() # type: ignore[attr-defined] - try: - rep = call_and_report(item, "setup", log) - reports = [rep] - if rep.passed: - setup_only = item.config.getoption("setuponly", False) - if item.config.getoption("setupshow", False): - show_test_item(item, add_space=not setup_only) - if not setup_only: - reports.append(call_and_report(item, "call", log)) - # If the session is about to fail or stop, teardown everything - this is - # necessary to correctly report fixture teardown errors (see #11706) - if item.session.shouldfail or item.session.shouldstop: - nextitem = None - reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) - finally: - # After all teardown hooks have been called (or an exception was reraised) - # want funcargs and request info to go away. - if hasrequest: - item._request = False # type: ignore[attr-defined] - item.funcargs = None # type: ignore[attr-defined] - return reports - - -def show_test_item(item: Item, *, add_space: bool) -> None: - """Show test function, parameters and the fixtures of the test item.""" - tw = item.config.get_terminal_writer() - tw.line() - tw.write(" " * 8) - tw.write(item.nodeid) - used_fixtures = sorted(getattr(item, "fixturenames", [])) - if used_fixtures: - tw.write(f" (fixtures used: {', '.join(used_fixtures)})") - if add_space: - tw.write(" ") - tw.flush() - - -def pytest_runtest_setup(item: Item) -> None: - _update_current_test_var(item, "setup") - item.session._setupstate.setup(item) - - -def pytest_runtest_call(item: Item) -> None: - _update_current_test_var(item, "call") - try: - del sys.last_type - del sys.last_value - del sys.last_traceback - if sys.version_info >= (3, 12, 0): - del sys.last_exc # type:ignore[attr-defined] - except AttributeError: - pass - try: - item.runtest() - except Exception as e: - # Store trace info to allow postmortem debugging - sys.last_type = type(e) - sys.last_value = e - if sys.version_info >= (3, 12, 0): - sys.last_exc = e # type:ignore[attr-defined] - assert e.__traceback__ is not None - # Skip *this* frame - sys.last_traceback = e.__traceback__.tb_next - raise - - -def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: - _update_current_test_var(item, "teardown") - item.session._setupstate.teardown_exact(nextitem) - _update_current_test_var(item, None) - - -def _update_current_test_var( - item: Item, when: Literal["setup", "call", "teardown"] | None -) -> None: - """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. - - If ``when`` is None, delete ``PYTEST_CURRENT_TEST`` from the environment. - """ - var_name = "PYTEST_CURRENT_TEST" - if when: - value = f"{item.nodeid} ({when})" - # don't allow null bytes on environment variables (see #2644, #2957) - value = value.replace("\x00", "(null)") - os.environ[var_name] = value - else: - os.environ.pop(var_name) - - -def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: - if report.when in ("setup", "teardown"): - if report.failed: - # category, shortletter, verbose-word - return "error", "E", "ERROR" - elif report.skipped: - return "skipped", "s", "SKIPPED" - else: - return "", "", "" - return None - - -# -# Implementation - - -def call_and_report( - item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds -) -> TestReport: - ihook = item.ihook - if when == "setup": - runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup - elif when == "call": - runtest_hook = ihook.pytest_runtest_call - elif when == "teardown": - runtest_hook = ihook.pytest_runtest_teardown - else: - assert False, f"Unhandled runtest hook case: {when}" - - call = CallInfo.from_call( - lambda: runtest_hook(item=item, **kwds), - when=when, - reraise=get_reraise_exceptions(item.config), - ) - report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) - if log: - ihook.pytest_runtest_logreport(report=report) - if check_interactive_exception(call, report): - ihook.pytest_exception_interact(node=item, call=call, report=report) - return report - - -def get_reraise_exceptions(config: Config) -> tuple[type[BaseException], ...]: - """Return exception types that should not be suppressed in general.""" - reraise: tuple[type[BaseException], ...] = (Exit,) - if not config.getoption("usepdb", False): - reraise += (KeyboardInterrupt,) - return reraise - - -def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool: - """Check whether the call raised an exception that should be reported as - interactive.""" - if call.excinfo is None: - # Didn't raise. - return False - if hasattr(report, "wasxfail"): - # Exception was expected. - return False - unittest = sys.modules.get("unittest") - if isinstance(call.excinfo.value, Skipped | bdb.BdbQuit) or ( - unittest is not None and isinstance(call.excinfo.value, unittest.SkipTest) - ): - # Special control flow exception. - return False - return True - - -TResult = TypeVar("TResult", covariant=True) - - -@final -@dataclasses.dataclass -class CallInfo(Generic[TResult]): - """Result/Exception info of a function invocation.""" - - _result: TResult | None - #: The captured exception of the call, if it raised. - excinfo: ExceptionInfo[BaseException] | None - #: The system time when the call started, in seconds since the epoch. - start: float - #: The system time when the call ended, in seconds since the epoch. - stop: float - #: The call duration, in seconds. - duration: float - #: The context of invocation: "collect", "setup", "call" or "teardown". - when: Literal["collect", "setup", "call", "teardown"] - - def __init__( - self, - result: TResult | None, - excinfo: ExceptionInfo[BaseException] | None, - start: float, - stop: float, - duration: float, - when: Literal["collect", "setup", "call", "teardown"], - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self._result = result - self.excinfo = excinfo - self.start = start - self.stop = stop - self.duration = duration - self.when = when - - @property - def result(self) -> TResult: - """The return value of the call, if it didn't raise. - - Can only be accessed if excinfo is None. - """ - if self.excinfo is not None: - raise AttributeError(f"{self!r} has no valid result") - # The cast is safe because an exception wasn't raised, hence - # _result has the expected function return type (which may be - # None, that's why a cast and not an assert). - return cast(TResult, self._result) - - @classmethod - def from_call( - cls, - func: Callable[[], TResult], - when: Literal["collect", "setup", "call", "teardown"], - reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, - ) -> CallInfo[TResult]: - """Call func, wrapping the result in a CallInfo. - - :param func: - The function to call. Called without arguments. - :type func: Callable[[], _pytest.runner.TResult] - :param when: - The phase in which the function is called. - :param reraise: - Exception or exceptions that shall propagate if raised by the - function, instead of being wrapped in the CallInfo. - """ - excinfo = None - instant = timing.Instant() - try: - result: TResult | None = func() - except BaseException: - excinfo = ExceptionInfo.from_current() - if reraise is not None and isinstance(excinfo.value, reraise): - raise - result = None - duration = instant.elapsed() - return cls( - start=duration.start.time, - stop=duration.stop.time, - duration=duration.seconds, - when=when, - result=result, - excinfo=excinfo, - _ispytest=True, - ) - - def __repr__(self) -> str: - if self.excinfo is None: - return f"" - return f"" - - -def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: - return TestReport.from_item_and_call(item, call) - - -def pytest_make_collect_report(collector: Collector) -> CollectReport: - def collect() -> list[Item | Collector]: - # Before collecting, if this is a Directory, load the conftests. - # If a conftest import fails to load, it is considered a collection - # error of the Directory collector. This is why it's done inside of the - # CallInfo wrapper. - # - # Note: initial conftests are loaded early, not here. - if isinstance(collector, Directory): - collector.config.pluginmanager._loadconftestmodules( - collector.path, - collector.config.getoption("importmode"), - rootpath=collector.config.rootpath, - consider_namespace_packages=collector.config.getini( - "consider_namespace_packages" - ), - ) - - return list(collector.collect()) - - call = CallInfo.from_call( - collect, "collect", reraise=(KeyboardInterrupt, SystemExit) - ) - longrepr: None | tuple[str, int, str] | str | TerminalRepr = None - if not call.excinfo: - outcome: Literal["passed", "skipped", "failed"] = "passed" - else: - skip_exceptions = [Skipped] - unittest = sys.modules.get("unittest") - if unittest is not None: - skip_exceptions.append(unittest.SkipTest) - if isinstance(call.excinfo.value, tuple(skip_exceptions)): - outcome = "skipped" - r_ = collector._repr_failure_py(call.excinfo, "line") - assert isinstance(r_, ExceptionChainRepr), repr(r_) - r = r_.reprcrash - assert r - longrepr = (str(r.path), r.lineno, r.message) - else: - outcome = "failed" - errorinfo = collector.repr_failure(call.excinfo) - if not hasattr(errorinfo, "toterminal"): - assert isinstance(errorinfo, str) - errorinfo = CollectErrorRepr(errorinfo) - longrepr = errorinfo - result = call.result if not call.excinfo else None - rep = CollectReport(collector.nodeid, outcome, longrepr, result) - rep.call = call # type: ignore # see collect_one_node - return rep - - -class SetupState: - """Shared state for setting up/tearing down test items or collectors - in a session. - - Suppose we have a collection tree as follows: - - - - - - - - The SetupState maintains a stack. The stack starts out empty: - - [] - - During the setup phase of item1, setup(item1) is called. What it does - is: - - push session to stack, run session.setup() - push mod1 to stack, run mod1.setup() - push item1 to stack, run item1.setup() - - The stack is: - - [session, mod1, item1] - - While the stack is in this shape, it is allowed to add finalizers to - each of session, mod1, item1 using addfinalizer(). - - During the teardown phase of item1, teardown_exact(item2) is called, - where item2 is the next item to item1. What it does is: - - pop item1 from stack, run its teardowns - pop mod1 from stack, run its teardowns - - mod1 was popped because it ended its purpose with item1. The stack is: - - [session] - - During the setup phase of item2, setup(item2) is called. What it does - is: - - push mod2 to stack, run mod2.setup() - push item2 to stack, run item2.setup() - - Stack: - - [session, mod2, item2] - - During the teardown phase of item2, teardown_exact(None) is called, - because item2 is the last item. What it does is: - - pop item2 from stack, run its teardowns - pop mod2 from stack, run its teardowns - pop session from stack, run its teardowns - - Stack: - - [] - - The end! - """ - - def __init__(self) -> None: - # The stack is in the dict insertion order. - self.stack: dict[ - Node, - tuple[ - # Node's finalizers. - list[Callable[[], object]], - # Node's exception and original traceback, if its setup raised. - tuple[OutcomeException | Exception, types.TracebackType | None] | None, - ], - ] = {} - - def is_node_active(self, node: Node) -> bool: - """Check if a node is currently active in the stack -- set up and not - torn down yet.""" - return node in self.stack - - def setup(self, item: Item) -> None: - """Setup objects along the collector chain to the item.""" - needed_collectors = item.listchain() - - # If a collector fails its setup, fail its entire subtree of items. - # The setup is not retried for each item - the same exception is used. - for col, (finalizers, exc) in self.stack.items(): - assert col in needed_collectors, "previous item was not torn down properly" - if exc: - raise exc[0].with_traceback(exc[1]) - - for col in needed_collectors[len(self.stack) :]: - assert col not in self.stack - # Push onto the stack. - self.stack[col] = ([col.teardown], None) - try: - col.setup() - except TEST_OUTCOME as exc: - self.stack[col] = (self.stack[col][0], (exc, exc.__traceback__)) - raise - - def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: - """Attach a finalizer to the given node. - - The node must be currently active in the stack. - """ - assert node and not isinstance(node, tuple) - assert callable(finalizer) - assert node in self.stack, (node, self.stack) - self.stack[node][0].append(finalizer) - - def teardown_exact(self, nextitem: Item | None) -> None: - """Teardown the current stack up until reaching nodes that nextitem - also descends from. - - When nextitem is None (meaning we're at the last item), the entire - stack is torn down. - """ - needed_collectors = (nextitem and nextitem.listchain()) or [] - exceptions: list[BaseException] = [] - while self.stack: - if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: - break - node, (finalizers, _) = self.stack.popitem() - these_exceptions = [] - while finalizers: - fin = finalizers.pop() - try: - fin() - except TEST_OUTCOME as e: - these_exceptions.append(e) - - if len(these_exceptions) == 1: - exceptions.extend(these_exceptions) - elif these_exceptions: - msg = f"errors while tearing down {node!r}" - exceptions.append(BaseExceptionGroup(msg, these_exceptions[::-1])) - - if len(exceptions) == 1: - raise exceptions[0] - elif exceptions: - raise BaseExceptionGroup("errors during test teardown", exceptions[::-1]) - if nextitem is None: - assert not self.stack - - -def collect_one_node(collector: Collector) -> CollectReport: - ihook = collector.ihook - ihook.pytest_collectstart(collector=collector) - rep: CollectReport = ihook.pytest_make_collect_report(collector=collector) - call = rep.__dict__.pop("call", None) - if call and check_interactive_exception(call, rep): - ihook.pytest_exception_interact(node=collector, call=call, report=rep) - return rep diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/scope.py b/tests/venv2/lib/python3.11/site-packages/_pytest/scope.py deleted file mode 100644 index 68a9da0..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/scope.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -Scope definition and related utilities. - -Those are defined here, instead of in the 'fixtures' module because -their use is spread across many other pytest modules, and centralizing it in 'fixtures' -would cause circular references. - -Also this makes the module light to import, as it should. -""" - -from __future__ import annotations - -from enum import Enum -from functools import total_ordering -from typing import Literal - - -ScopeName = Literal["session", "package", "module", "class", "function"] - - -@total_ordering -class Scope(Enum): - """ - Represents one of the possible fixture scopes in pytest. - - Scopes are ordered from lower to higher, that is: - - ->>> higher ->>> - - Function < Class < Module < Package < Session - - <<<- lower <<<- - """ - - # Scopes need to be listed from lower to higher. - Function = "function" - Class = "class" - Module = "module" - Package = "package" - Session = "session" - - def next_lower(self) -> Scope: - """Return the next lower scope.""" - index = _SCOPE_INDICES[self] - if index == 0: - raise ValueError(f"{self} is the lower-most scope") - return _ALL_SCOPES[index - 1] - - def next_higher(self) -> Scope: - """Return the next higher scope.""" - index = _SCOPE_INDICES[self] - if index == len(_SCOPE_INDICES) - 1: - raise ValueError(f"{self} is the upper-most scope") - return _ALL_SCOPES[index + 1] - - def __lt__(self, other: Scope) -> bool: - self_index = _SCOPE_INDICES[self] - other_index = _SCOPE_INDICES[other] - return self_index < other_index - - @classmethod - def from_user( - cls, scope_name: ScopeName, descr: str, where: str | None = None - ) -> Scope: - """ - Given a scope name from the user, return the equivalent Scope enum. Should be used - whenever we want to convert a user provided scope name to its enum object. - - If the scope name is invalid, construct a user friendly message and call pytest.fail. - """ - from _pytest.outcomes import fail - - try: - # Holding this reference is necessary for mypy at the moment. - scope = Scope(scope_name) - except ValueError: - fail( - "{} {}got an unexpected scope value '{}'".format( - descr, f"from {where} " if where else "", scope_name - ), - pytrace=False, - ) - return scope - - -_ALL_SCOPES = list(Scope) -_SCOPE_INDICES = {scope: index for index, scope in enumerate(_ALL_SCOPES)} - - -# Ordered list of scopes which can contain many tests (in practice all except Function). -HIGH_SCOPES = [x for x in Scope if x is not Scope.Function] diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/setuponly.py b/tests/venv2/lib/python3.11/site-packages/_pytest/setuponly.py deleted file mode 100644 index 7e6b46b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/setuponly.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import annotations - -from collections.abc import Generator - -from _pytest._io.saferepr import saferepr -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config.argparsing import Parser -from _pytest.fixtures import FixtureDef -from _pytest.fixtures import SubRequest -from _pytest.scope import Scope -import pytest - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("debugconfig") - group.addoption( - "--setuponly", - "--setup-only", - action="store_true", - help="Only setup fixtures, do not execute tests", - ) - group.addoption( - "--setupshow", - "--setup-show", - action="store_true", - help="Show setup of fixtures while executing tests", - ) - - -@pytest.hookimpl(wrapper=True) -def pytest_fixture_setup( - fixturedef: FixtureDef[object], request: SubRequest -) -> Generator[None, object, object]: - try: - return (yield) - finally: - if request.config.option.setupshow: - if hasattr(request, "param"): - # Save the fixture parameter so ._show_fixture_action() can - # display it now and during the teardown (in .finish()). - if fixturedef.ids: - if callable(fixturedef.ids): - param = fixturedef.ids(request.param) - else: - param = fixturedef.ids[request.param_index] - else: - param = request.param - fixturedef.cached_param = param # type: ignore[attr-defined] - _show_fixture_action(fixturedef, request.config, "SETUP") - - -def pytest_fixture_post_finalizer( - fixturedef: FixtureDef[object], request: SubRequest -) -> None: - if fixturedef.cached_result is not None: - config = request.config - if config.option.setupshow: - _show_fixture_action(fixturedef, request.config, "TEARDOWN") - if hasattr(fixturedef, "cached_param"): - del fixturedef.cached_param - - -def _show_fixture_action( - fixturedef: FixtureDef[object], config: Config, msg: str -) -> None: - capman = config.pluginmanager.getplugin("capturemanager") - if capman: - capman.suspend_global_capture() - - tw = config.get_terminal_writer() - tw.line() - # Use smaller indentation the higher the scope: Session = 0, Package = 1, etc. - scope_indent = list(reversed(Scope)).index(fixturedef._scope) - tw.write(" " * 2 * scope_indent) - - scopename = fixturedef.scope[0].upper() - tw.write(f"{msg:<8} {scopename} {fixturedef.argname}") - - if msg == "SETUP": - deps = sorted(arg for arg in fixturedef.argnames if arg != "request") - if deps: - tw.write(" (fixtures used: {})".format(", ".join(deps))) - - if hasattr(fixturedef, "cached_param"): - tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") - - tw.flush() - - if capman: - capman.resume_global_capture() - - -@pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - if config.option.setuponly: - config.option.setupshow = True - return None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/setupplan.py b/tests/venv2/lib/python3.11/site-packages/_pytest/setupplan.py deleted file mode 100644 index 4e124cc..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/setupplan.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config.argparsing import Parser -from _pytest.fixtures import FixtureDef -from _pytest.fixtures import SubRequest -import pytest - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("debugconfig") - group.addoption( - "--setupplan", - "--setup-plan", - action="store_true", - help="Show what fixtures and tests would be executed but " - "don't execute anything", - ) - - -@pytest.hookimpl(tryfirst=True) -def pytest_fixture_setup( - fixturedef: FixtureDef[object], request: SubRequest -) -> object | None: - # Will return a dummy fixture if the setuponly option is provided. - if request.config.option.setupplan: - my_cache_key = fixturedef.cache_key(request) - fixturedef.cached_result = (None, my_cache_key, None) - return fixturedef.cached_result - return None - - -@pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - if config.option.setupplan: - config.option.setuponly = True - config.option.setupshow = True - return None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/skipping.py b/tests/venv2/lib/python3.11/site-packages/_pytest/skipping.py deleted file mode 100644 index f7a4c4c..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/skipping.py +++ /dev/null @@ -1,321 +0,0 @@ -# mypy: allow-untyped-defs -"""Support for skip/xfail functions and markers.""" - -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Mapping -import dataclasses -import os -import platform -import sys -import traceback - -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.mark.structures import Mark -from _pytest.nodes import Item -from _pytest.outcomes import fail -from _pytest.outcomes import skip -from _pytest.outcomes import xfail -from _pytest.raises import AbstractRaises -from _pytest.reports import BaseReport -from _pytest.reports import TestReport -from _pytest.runner import CallInfo -from _pytest.stash import StashKey - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group.addoption( - "--runxfail", - action="store_true", - dest="runxfail", - default=False, - help="Report the results of xfail tests as if they were not marked", - ) - - parser.addini( - "strict_xfail", - "Default for the strict parameter of xfail " - "markers when not given explicitly (default: False) (alias: xfail_strict)", - type="bool", - # None => fallback to `strict`. - default=None, - aliases=["xfail_strict"], - ) - - -def pytest_configure(config: Config) -> None: - if config.option.runxfail: - # yay a hack - import pytest - - old = pytest.xfail - config.add_cleanup(lambda: setattr(pytest, "xfail", old)) - - def nop(*args, **kwargs): - pass - - nop.Exception = xfail.Exception # type: ignore[attr-defined] - setattr(pytest, "xfail", nop) - - config.addinivalue_line( - "markers", - "skip(reason=None): skip the given test function with an optional reason. " - 'Example: skip(reason="no way of currently testing this") skips the ' - "test.", - ) - config.addinivalue_line( - "markers", - "skipif(condition, ..., *, reason=...): " - "skip the given test function if any of the conditions evaluate to True. " - "Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. " - "See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-skipif", - ) - config.addinivalue_line( - "markers", - "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=strict_xfail): " - "mark the test function as an expected failure if any of the conditions " - "evaluate to True. Optionally specify a reason for better reporting " - "and run=False if you don't even want to execute the test function. " - "If only specific exception(s) are expected, you can list them in " - "raises, and if the test fails in other ways, it will be reported as " - "a true failure. See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-xfail", - ) - - -def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]: - """Evaluate a single skipif/xfail condition. - - If an old-style string condition is given, it is eval()'d, otherwise the - condition is bool()'d. If this fails, an appropriately formatted pytest.fail - is raised. - - Returns (result, reason). The reason is only relevant if the result is True. - """ - # String condition. - if isinstance(condition, str): - globals_ = { - "os": os, - "sys": sys, - "platform": platform, - "config": item.config, - } - for dictionary in reversed( - item.ihook.pytest_markeval_namespace(config=item.config) - ): - if not isinstance(dictionary, Mapping): - raise ValueError( - f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}" - ) - globals_.update(dictionary) - if hasattr(item, "obj"): - globals_.update(item.obj.__globals__) - try: - filename = f"<{mark.name} condition>" - condition_code = compile(condition, filename, "eval") - result = eval(condition_code, globals_) - except SyntaxError as exc: - msglines = [ - f"Error evaluating {mark.name!r} condition", - " " + condition, - " " + " " * (exc.offset or 0) + "^", - "SyntaxError: invalid syntax", - ] - fail("\n".join(msglines), pytrace=False) - except Exception as exc: - msglines = [ - f"Error evaluating {mark.name!r} condition", - " " + condition, - *traceback.format_exception_only(exc), - ] - fail("\n".join(msglines), pytrace=False) - - # Boolean condition. - else: - try: - result = bool(condition) - except Exception as exc: - msglines = [ - f"Error evaluating {mark.name!r} condition as a boolean", - *traceback.format_exception_only(exc), - ] - fail("\n".join(msglines), pytrace=False) - - reason = mark.kwargs.get("reason", None) - if reason is None: - if isinstance(condition, str): - reason = "condition: " + condition - else: - # XXX better be checked at collection time - msg = ( - f"Error evaluating {mark.name!r}: " - + "you need to specify reason=STRING when using booleans as conditions." - ) - fail(msg, pytrace=False) - - return result, reason - - -@dataclasses.dataclass(frozen=True) -class Skip: - """The result of evaluate_skip_marks().""" - - reason: str = "unconditional skip" - - -def evaluate_skip_marks(item: Item) -> Skip | None: - """Evaluate skip and skipif marks on item, returning Skip if triggered.""" - for mark in item.iter_markers(name="skipif"): - if "condition" not in mark.kwargs: - conditions = mark.args - else: - conditions = (mark.kwargs["condition"],) - - # Unconditional. - if not conditions: - reason = mark.kwargs.get("reason", "") - return Skip(reason) - - # If any of the conditions are true. - for condition in conditions: - result, reason = evaluate_condition(item, mark, condition) - if result: - return Skip(reason) - - for mark in item.iter_markers(name="skip"): - try: - return Skip(*mark.args, **mark.kwargs) - except TypeError as e: - raise TypeError(str(e) + " - maybe you meant pytest.mark.skipif?") from None - - return None - - -@dataclasses.dataclass(frozen=True) -class Xfail: - """The result of evaluate_xfail_marks().""" - - __slots__ = ("raises", "reason", "run", "strict") - - reason: str - run: bool - strict: bool - raises: ( - type[BaseException] - | tuple[type[BaseException], ...] - | AbstractRaises[BaseException] - | None - ) - - -def evaluate_xfail_marks(item: Item) -> Xfail | None: - """Evaluate xfail marks on item, returning Xfail if triggered.""" - for mark in item.iter_markers(name="xfail"): - run = mark.kwargs.get("run", True) - strict = mark.kwargs.get("strict") - if strict is None: - strict = item.config.getini("strict_xfail") - if strict is None: - strict = item.config.getini("strict") - raises = mark.kwargs.get("raises", None) - if "condition" not in mark.kwargs: - conditions = mark.args - else: - conditions = (mark.kwargs["condition"],) - - # Unconditional. - if not conditions: - reason = mark.kwargs.get("reason", "") - return Xfail(reason, run, strict, raises) - - # If any of the conditions are true. - for condition in conditions: - result, reason = evaluate_condition(item, mark, condition) - if result: - return Xfail(reason, run, strict, raises) - - return None - - -# Saves the xfail mark evaluation. Can be refreshed during call if None. -xfailed_key = StashKey[Xfail | None]() - - -@hookimpl(tryfirst=True) -def pytest_runtest_setup(item: Item) -> None: - skipped = evaluate_skip_marks(item) - if skipped: - raise skip.Exception(skipped.reason, _use_item_location=True) - - item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) - if xfailed and not item.config.option.runxfail and not xfailed.run: - xfail("[NOTRUN] " + xfailed.reason) - - -@hookimpl(wrapper=True) -def pytest_runtest_call(item: Item) -> Generator[None]: - xfailed = item.stash.get(xfailed_key, None) - if xfailed is None: - item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) - - if xfailed and not item.config.option.runxfail and not xfailed.run: - xfail("[NOTRUN] " + xfailed.reason) - - try: - return (yield) - finally: - # The test run may have added an xfail mark dynamically. - xfailed = item.stash.get(xfailed_key, None) - if xfailed is None: - item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) - - -@hookimpl(wrapper=True) -def pytest_runtest_makereport( - item: Item, call: CallInfo[None] -) -> Generator[None, TestReport, TestReport]: - rep = yield - xfailed = item.stash.get(xfailed_key, None) - if item.config.option.runxfail: - pass # don't interfere - elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception): - assert call.excinfo.value.msg is not None - rep.wasxfail = call.excinfo.value.msg - rep.outcome = "skipped" - elif not rep.skipped and xfailed: - if call.excinfo: - raises = xfailed.raises - if raises is None or ( - ( - isinstance(raises, type | tuple) - and isinstance(call.excinfo.value, raises) - ) - or ( - isinstance(raises, AbstractRaises) - and raises.matches(call.excinfo.value) - ) - ): - rep.outcome = "skipped" - rep.wasxfail = xfailed.reason - else: - rep.outcome = "failed" - elif call.when == "call": - if xfailed.strict: - rep.outcome = "failed" - rep.longrepr = "[XPASS(strict)] " + xfailed.reason - else: - rep.outcome = "passed" - rep.wasxfail = xfailed.reason - return rep - - -def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: - if hasattr(report, "wasxfail"): - if report.skipped: - return "xfailed", "x", "XFAIL" - elif report.passed: - return "xpassed", "X", "XPASS" - return None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/stash.py b/tests/venv2/lib/python3.11/site-packages/_pytest/stash.py deleted file mode 100644 index 6a9ff88..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/stash.py +++ /dev/null @@ -1,116 +0,0 @@ -from __future__ import annotations - -from typing import Any -from typing import cast -from typing import Generic -from typing import TypeVar - - -__all__ = ["Stash", "StashKey"] - - -T = TypeVar("T") -D = TypeVar("D") - - -class StashKey(Generic[T]): - """``StashKey`` is an object used as a key to a :class:`Stash`. - - A ``StashKey`` is associated with the type ``T`` of the value of the key. - - A ``StashKey`` is unique and cannot conflict with another key. - - .. versionadded:: 7.0 - """ - - __slots__ = () - - -class Stash: - r"""``Stash`` is a type-safe heterogeneous mutable mapping that - allows keys and value types to be defined separately from - where it (the ``Stash``) is created. - - Usually you will be given an object which has a ``Stash``, for example - :class:`~pytest.Config` or a :class:`~_pytest.nodes.Node`: - - .. code-block:: python - - stash: Stash = some_object.stash - - If a module or plugin wants to store data in this ``Stash``, it creates - :class:`StashKey`\s for its keys (at the module level): - - .. code-block:: python - - # At the top-level of the module - some_str_key = StashKey[str]() - some_bool_key = StashKey[bool]() - - To store information: - - .. code-block:: python - - # Value type must match the key. - stash[some_str_key] = "value" - stash[some_bool_key] = True - - To retrieve the information: - - .. code-block:: python - - # The static type of some_str is str. - some_str = stash[some_str_key] - # The static type of some_bool is bool. - some_bool = stash[some_bool_key] - - .. versionadded:: 7.0 - """ - - __slots__ = ("_storage",) - - def __init__(self) -> None: - self._storage: dict[StashKey[Any], object] = {} - - def __setitem__(self, key: StashKey[T], value: T) -> None: - """Set a value for key.""" - self._storage[key] = value - - def __getitem__(self, key: StashKey[T]) -> T: - """Get the value for key. - - Raises ``KeyError`` if the key wasn't set before. - """ - return cast(T, self._storage[key]) - - def get(self, key: StashKey[T], default: D) -> T | D: - """Get the value for key, or return default if the key wasn't set - before.""" - try: - return self[key] - except KeyError: - return default - - def setdefault(self, key: StashKey[T], default: T) -> T: - """Return the value of key if already set, otherwise set the value - of key to default and return default.""" - try: - return self[key] - except KeyError: - self[key] = default - return default - - def __delitem__(self, key: StashKey[T]) -> None: - """Delete the value for key. - - Raises ``KeyError`` if the key wasn't set before. - """ - del self._storage[key] - - def __contains__(self, key: StashKey[T]) -> bool: - """Return whether key was set.""" - return key in self._storage - - def __len__(self) -> int: - """Return how many items exist in the stash.""" - return len(self._storage) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/stepwise.py b/tests/venv2/lib/python3.11/site-packages/_pytest/stepwise.py deleted file mode 100644 index 8901540..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/stepwise.py +++ /dev/null @@ -1,209 +0,0 @@ -from __future__ import annotations - -import dataclasses -from datetime import datetime -from datetime import timedelta -from typing import Any -from typing import TYPE_CHECKING - -from _pytest import nodes -from _pytest.cacheprovider import Cache -from _pytest.config import Config -from _pytest.config.argparsing import Parser -from _pytest.main import Session -from _pytest.reports import TestReport - - -if TYPE_CHECKING: - from typing_extensions import Self - -STEPWISE_CACHE_DIR = "cache/stepwise" - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group.addoption( - "--sw", - "--stepwise", - action="store_true", - default=False, - dest="stepwise", - help="Exit on test failure and continue from last failing test next time", - ) - group.addoption( - "--sw-skip", - "--stepwise-skip", - action="store_true", - default=False, - dest="stepwise_skip", - help="Ignore the first failing test but stop on the next failing test. " - "Implicitly enables --stepwise.", - ) - group.addoption( - "--sw-reset", - "--stepwise-reset", - action="store_true", - default=False, - dest="stepwise_reset", - help="Resets stepwise state, restarting the stepwise workflow. " - "Implicitly enables --stepwise.", - ) - - -def pytest_configure(config: Config) -> None: - # --stepwise-skip/--stepwise-reset implies stepwise. - if config.option.stepwise_skip or config.option.stepwise_reset: - config.option.stepwise = True - if config.getoption("stepwise"): - config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") - - -def pytest_sessionfinish(session: Session) -> None: - if not session.config.getoption("stepwise"): - assert session.config.cache is not None - if hasattr(session.config, "workerinput"): - # Do not update cache if this process is a xdist worker to prevent - # race conditions (#10641). - return - - -@dataclasses.dataclass -class StepwiseCacheInfo: - # The nodeid of the last failed test. - last_failed: str | None - - # The number of tests in the last time --stepwise was run. - # We use this information as a simple way to invalidate the cache information, avoiding - # confusing behavior in case the cache is stale. - last_test_count: int | None - - # The date when the cache was last updated, for information purposes only. - last_cache_date_str: str - - @property - def last_cache_date(self) -> datetime: - return datetime.fromisoformat(self.last_cache_date_str) - - @classmethod - def empty(cls) -> Self: - return cls( - last_failed=None, - last_test_count=None, - last_cache_date_str=datetime.now().isoformat(), - ) - - def update_date_to_now(self) -> None: - self.last_cache_date_str = datetime.now().isoformat() - - -class StepwisePlugin: - def __init__(self, config: Config) -> None: - self.config = config - self.session: Session | None = None - self.report_status: list[str] = [] - assert config.cache is not None - self.cache: Cache = config.cache - self.skip: bool = config.getoption("stepwise_skip") - self.reset: bool = config.getoption("stepwise_reset") - self.cached_info = self._load_cached_info() - - def _load_cached_info(self) -> StepwiseCacheInfo: - cached_dict: dict[str, Any] | None = self.cache.get(STEPWISE_CACHE_DIR, None) - if cached_dict: - try: - return StepwiseCacheInfo( - cached_dict["last_failed"], - cached_dict["last_test_count"], - cached_dict["last_cache_date_str"], - ) - except (KeyError, TypeError) as e: - error = f"{type(e).__name__}: {e}" - self.report_status.append(f"error reading cache, discarding ({error})") - - # Cache not found or error during load, return a new cache. - return StepwiseCacheInfo.empty() - - def pytest_sessionstart(self, session: Session) -> None: - self.session = session - - def pytest_collection_modifyitems( - self, config: Config, items: list[nodes.Item] - ) -> None: - last_test_count = self.cached_info.last_test_count - self.cached_info.last_test_count = len(items) - - if self.reset: - self.report_status.append("resetting state, not skipping.") - self.cached_info.last_failed = None - return - - if not self.cached_info.last_failed: - self.report_status.append("no previously failed tests, not skipping.") - return - - if last_test_count is not None and last_test_count != len(items): - self.report_status.append( - f"test count changed, not skipping (now {len(items)} tests, previously {last_test_count})." - ) - self.cached_info.last_failed = None - return - - # Check all item nodes until we find a match on last failed. - failed_index = None - for index, item in enumerate(items): - if item.nodeid == self.cached_info.last_failed: - failed_index = index - break - - # If the previously failed test was not found among the test items, - # do not skip any tests. - if failed_index is None: - self.report_status.append("previously failed test not found, not skipping.") - else: - cache_age = datetime.now() - self.cached_info.last_cache_date - # Round up to avoid showing microseconds. - cache_age = timedelta(seconds=int(cache_age.total_seconds())) - self.report_status.append( - f"skipping {failed_index} already passed items (cache from {cache_age} ago," - f" use --sw-reset to discard)." - ) - deselected = items[:failed_index] - del items[:failed_index] - config.hook.pytest_deselected(items=deselected) - - def pytest_runtest_logreport(self, report: TestReport) -> None: - if report.failed: - if self.skip: - # Remove test from the failed ones (if it exists) and unset the skip option - # to make sure the following tests will not be skipped. - if report.nodeid == self.cached_info.last_failed: - self.cached_info.last_failed = None - - self.skip = False - else: - # Mark test as the last failing and interrupt the test session. - self.cached_info.last_failed = report.nodeid - assert self.session is not None - self.session.shouldstop = ( - "Test failed, continuing from this test next run." - ) - - else: - # If the test was actually run and did pass. - if report.when == "call": - # Remove test from the failed ones, if exists. - if report.nodeid == self.cached_info.last_failed: - self.cached_info.last_failed = None - - def pytest_report_collectionfinish(self) -> list[str] | None: - if self.config.get_verbosity() >= 0 and self.report_status: - return [f"stepwise: {x}" for x in self.report_status] - return None - - def pytest_sessionfinish(self) -> None: - if hasattr(self.config, "workerinput"): - # Do not update cache if this process is a xdist worker to prevent - # race conditions (#10641). - return - self.cached_info.update_date_to_now() - self.cache.set(STEPWISE_CACHE_DIR, dataclasses.asdict(self.cached_info)) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/subtests.py b/tests/venv2/lib/python3.11/site-packages/_pytest/subtests.py deleted file mode 100644 index 6ac3b5c..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/subtests.py +++ /dev/null @@ -1,418 +0,0 @@ -"""Builtin plugin that adds subtests support.""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Callable -from collections.abc import Iterator -from collections.abc import Mapping -from contextlib import AbstractContextManager -from contextlib import contextmanager -from contextlib import ExitStack -from contextlib import nullcontext -import dataclasses -import time -from types import TracebackType -from typing import Any -from typing import TYPE_CHECKING - -import pluggy - -from _pytest._code import ExceptionInfo -from _pytest._io.saferepr import saferepr -from _pytest.capture import CaptureFixture -from _pytest.capture import FDCapture -from _pytest.capture import SysCapture -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import SubRequest -from _pytest.logging import catching_logs -from _pytest.logging import LogCaptureHandler -from _pytest.logging import LoggingPlugin -from _pytest.reports import TestReport -from _pytest.runner import CallInfo -from _pytest.runner import check_interactive_exception -from _pytest.runner import get_reraise_exceptions -from _pytest.stash import StashKey - - -if TYPE_CHECKING: - from typing_extensions import Self - - -def pytest_addoption(parser: Parser) -> None: - Config._add_verbosity_ini( - parser, - Config.VERBOSITY_SUBTESTS, - help=( - "Specify verbosity level for subtests. " - "Higher levels will generate output for passed subtests. Failed subtests are always reported." - ), - ) - - -@dataclasses.dataclass(frozen=True, slots=True, kw_only=True) -class SubtestContext: - """The values passed to Subtests.test() that are included in the test report.""" - - msg: str | None - kwargs: Mapping[str, Any] - - def __post_init__(self) -> None: - # Brute-force the returned kwargs dict to be JSON serializable (pytest-dev/pytest-xdist#1273). - object.__setattr__( - self, "kwargs", {k: saferepr(v) for (k, v) in self.kwargs.items()} - ) - - def _to_json(self) -> dict[str, Any]: - result = dataclasses.asdict(self) - return result - - @classmethod - def _from_json(cls, d: dict[str, Any]) -> Self: - return cls(msg=d["msg"], kwargs=d["kwargs"]) - - -@dataclasses.dataclass(init=False) -class SubtestReport(TestReport): - context: SubtestContext - - @property - def head_line(self) -> str: - _, _, domain = self.location - return f"{domain} {self._sub_test_description()}" - - def _sub_test_description(self) -> str: - parts = [] - if self.context.msg is not None: - parts.append(f"[{self.context.msg}]") - if self.context.kwargs: - params_desc = ", ".join( - f"{k}={v}" for (k, v) in self.context.kwargs.items() - ) - parts.append(f"({params_desc})") - return " ".join(parts) or "()" - - def _to_json(self) -> dict[str, Any]: - data = super()._to_json() - del data["context"] - data["_report_type"] = "SubTestReport" - data["_subtest.context"] = self.context._to_json() - return data - - @classmethod - def _from_json(cls, reportdict: dict[str, Any]) -> SubtestReport: - report = super()._from_json(reportdict) - report.context = SubtestContext._from_json(reportdict["_subtest.context"]) - return report - - @classmethod - def _new( - cls, - test_report: TestReport, - context: SubtestContext, - captured_output: Captured | None, - captured_logs: CapturedLogs | None, - ) -> Self: - result = super()._from_json(test_report._to_json()) - result.context = context - - if captured_output: - if captured_output.out: - result.sections.append(("Captured stdout call", captured_output.out)) - if captured_output.err: - result.sections.append(("Captured stderr call", captured_output.err)) - - if captured_logs and (log := captured_logs.handler.stream.getvalue()): - result.sections.append(("Captured log call", log)) - - return result - - -@fixture -def subtests(request: SubRequest) -> Subtests: - """Provides subtests functionality.""" - capmam = request.node.config.pluginmanager.get_plugin("capturemanager") - suspend_capture_ctx = ( - capmam.global_and_fixture_disabled if capmam is not None else nullcontext - ) - return Subtests(request.node.ihook, suspend_capture_ctx, request, _ispytest=True) - - -class Subtests: - """Subtests fixture, enables declaring subtests inside test functions via the :meth:`test` method.""" - - def __init__( - self, - ihook: pluggy.HookRelay, - suspend_capture_ctx: Callable[[], AbstractContextManager[None]], - request: SubRequest, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self._ihook = ihook - self._suspend_capture_ctx = suspend_capture_ctx - self._request = request - - def test( - self, - msg: str | None = None, - **kwargs: Any, - ) -> _SubTestContextManager: - """ - Context manager for subtests, capturing exceptions raised inside the subtest scope and - reporting assertion failures and errors individually. - - Usage - ----- - - .. code-block:: python - - def test(subtests): - for i in range(5): - with subtests.test("custom message", i=i): - assert i % 2 == 0 - - :param msg: - If given, the message will be shown in the test report in case of subtest failure. - - :param kwargs: - Arbitrary values that are also added to the subtest report. - """ - return _SubTestContextManager( - self._ihook, - msg, - kwargs, - request=self._request, - suspend_capture_ctx=self._suspend_capture_ctx, - config=self._request.config, - ) - - -@dataclasses.dataclass -class _SubTestContextManager: - """ - Context manager for subtests, capturing exceptions raised inside the subtest scope and handling - them through the pytest machinery. - """ - - # Note: initially the logic for this context manager was implemented directly - # in Subtests.test() as a @contextmanager, however, it is not possible to control the output fully when - # exiting from it due to an exception when in `--exitfirst` mode, so this was refactored into an - # explicit context manager class (pytest-dev/pytest-subtests#134). - - ihook: pluggy.HookRelay - msg: str | None - kwargs: dict[str, Any] - suspend_capture_ctx: Callable[[], AbstractContextManager[None]] - request: SubRequest - config: Config - - def __enter__(self) -> None: - __tracebackhide__ = True - - self._start = time.time() - self._precise_start = time.perf_counter() - self._exc_info = None - - self._exit_stack = ExitStack() - self._captured_output = self._exit_stack.enter_context( - capturing_output(self.request) - ) - self._captured_logs = self._exit_stack.enter_context( - capturing_logs(self.request) - ) - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - __tracebackhide__ = True - if exc_val is not None: - exc_info = ExceptionInfo.from_exception(exc_val) - else: - exc_info = None - - self._exit_stack.close() - - precise_stop = time.perf_counter() - duration = precise_stop - self._precise_start - stop = time.time() - - call_info = CallInfo[None]( - None, - exc_info, - start=self._start, - stop=stop, - duration=duration, - when="call", - _ispytest=True, - ) - report = self.ihook.pytest_runtest_makereport( - item=self.request.node, call=call_info - ) - sub_report = SubtestReport._new( - report, - SubtestContext(msg=self.msg, kwargs=self.kwargs), - captured_output=self._captured_output, - captured_logs=self._captured_logs, - ) - - if sub_report.failed: - failed_subtests = self.config.stash[failed_subtests_key] - failed_subtests[self.request.node.nodeid] += 1 - - with self.suspend_capture_ctx(): - self.ihook.pytest_runtest_logreport(report=sub_report) - - if check_interactive_exception(call_info, sub_report): - self.ihook.pytest_exception_interact( - node=self.request.node, call=call_info, report=sub_report - ) - - if exc_val is not None: - if isinstance(exc_val, get_reraise_exceptions(self.config)): - return False - if self.request.session.shouldfail: - return False - return True - - -@contextmanager -def capturing_output(request: SubRequest) -> Iterator[Captured]: - option = request.config.getoption("capture", None) - - capman = request.config.pluginmanager.getplugin("capturemanager") - if getattr(capman, "_capture_fixture", None): - # capsys or capfd are active, subtest should not capture. - fixture = None - elif option == "sys": - fixture = CaptureFixture(SysCapture, request, _ispytest=True) - elif option == "fd": - fixture = CaptureFixture(FDCapture, request, _ispytest=True) - else: - fixture = None - - if fixture is not None: - fixture._start() - - captured = Captured() - try: - yield captured - finally: - if fixture is not None: - out, err = fixture.readouterr() - fixture.close() - captured.out = out - captured.err = err - - -@contextmanager -def capturing_logs( - request: SubRequest, -) -> Iterator[CapturedLogs | None]: - logging_plugin: LoggingPlugin | None = request.config.pluginmanager.getplugin( - "logging-plugin" - ) - if logging_plugin is None: - yield None - else: - handler = LogCaptureHandler() - handler.setFormatter(logging_plugin.formatter) - - captured_logs = CapturedLogs(handler) - with catching_logs(handler, level=logging_plugin.log_level): - yield captured_logs - - -@dataclasses.dataclass -class Captured: - out: str = "" - err: str = "" - - -@dataclasses.dataclass -class CapturedLogs: - handler: LogCaptureHandler - - -def pytest_report_to_serializable(report: TestReport) -> dict[str, Any] | None: - if isinstance(report, SubtestReport): - return report._to_json() - return None - - -def pytest_report_from_serializable(data: dict[str, Any]) -> SubtestReport | None: - if data.get("_report_type") == "SubTestReport": - return SubtestReport._from_json(data) - return None - - -# Dict of nodeid -> number of failed subtests. -# Used to fail top-level tests that passed but contain failed subtests. -failed_subtests_key = StashKey[defaultdict[str, int]]() - - -def pytest_configure(config: Config) -> None: - config.stash[failed_subtests_key] = defaultdict(int) - - -@hookimpl(tryfirst=True) -def pytest_report_teststatus( - report: TestReport, - config: Config, -) -> tuple[str, str, str | Mapping[str, bool]] | None: - if report.when != "call": - return None - - quiet = config.get_verbosity(Config.VERBOSITY_SUBTESTS) == 0 - if isinstance(report, SubtestReport): - outcome = report.outcome - description = report._sub_test_description() - - if hasattr(report, "wasxfail"): - if quiet: - return "", "", "" - elif outcome == "skipped": - category = "xfailed" - short = "y" # x letter is used for regular xfail, y for subtest xfail - status = "SUBXFAIL" - # outcome == "passed" in an xfail is only possible via a @pytest.mark.xfail mark, which - # is not applicable to a subtest, which only handles pytest.xfail(). - else: # pragma: no cover - # This should not normally happen, unless some plugin is setting wasxfail without - # the correct outcome. Pytest expects the call outcome to be either skipped or - # passed in case of xfail. - # Let's pass this report to the next hook. - return None - return category, short, f"{status}{description}" - - if report.failed: - return outcome, "u", f"SUBFAILED{description}" - else: - if report.passed: - if quiet: - return "", "", "" - else: - return f"subtests {outcome}", "u", f"SUBPASSED{description}" - elif report.skipped: - if quiet: - return "", "", "" - else: - return outcome, "-", f"SUBSKIPPED{description}" - - else: - failed_subtests_count = config.stash[failed_subtests_key][report.nodeid] - # Top-level test, fail if it contains failed subtests and it has passed. - if report.passed and failed_subtests_count > 0: - report.outcome = "failed" - suffix = "s" if failed_subtests_count > 1 else "" - report.longrepr = f"contains {failed_subtests_count} failed subtest{suffix}" - - return None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/terminal.py b/tests/venv2/lib/python3.11/site-packages/_pytest/terminal.py deleted file mode 100644 index b9a65ff..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/terminal.py +++ /dev/null @@ -1,1786 +0,0 @@ -# mypy: allow-untyped-defs -"""Terminal reporting of the full testing process. - -This is a good source for looking at the various reporting hooks. -""" - -from __future__ import annotations - -import argparse -from collections import Counter -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Mapping -from collections.abc import Sequence -import dataclasses -import datetime -from functools import partial -import inspect -from pathlib import Path -import platform -import sys -import textwrap -from typing import Any -from typing import ClassVar -from typing import final -from typing import Literal -from typing import NamedTuple -from typing import TextIO -from typing import TYPE_CHECKING -import warnings - -import pluggy - -from _pytest import compat -from _pytest import nodes -from _pytest import timing -from _pytest._code import ExceptionInfo -from _pytest._code.code import ExceptionRepr -from _pytest._io import TerminalWriter -from _pytest._io.wcwidth import wcswidth -import _pytest._version -from _pytest.compat import running_on_ci -from _pytest.config import _PluggyPlugin -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.nodes import Item -from _pytest.nodes import Node -from _pytest.pathlib import absolutepath -from _pytest.pathlib import bestrelpath -from _pytest.reports import BaseReport -from _pytest.reports import CollectReport -from _pytest.reports import TestReport - - -if TYPE_CHECKING: - from _pytest.main import Session - - -REPORT_COLLECTING_RESOLUTION = 0.5 - -KNOWN_TYPES = ( - "failed", - "passed", - "skipped", - "deselected", - "xfailed", - "xpassed", - "warnings", - "error", - "subtests passed", - "subtests failed", - "subtests skipped", -) - -_REPORTCHARS_DEFAULT = "fE" - - -class MoreQuietAction(argparse.Action): - """A modified copy of the argparse count action which counts down and updates - the legacy quiet attribute at the same time. - - Used to unify verbosity handling. - """ - - def __init__( - self, - option_strings: Sequence[str], - dest: str, - default: object = None, - required: bool = False, - help: str | None = None, - ) -> None: - super().__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - default=default, - required=required, - help=help, - ) - - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: str | Sequence[object] | None, - option_string: str | None = None, - ) -> None: - new_count = getattr(namespace, self.dest, 0) - 1 - setattr(namespace, self.dest, new_count) - # todo Deprecate config.quiet - namespace.quiet = getattr(namespace, "quiet", 0) + 1 - - -class TestShortLogReport(NamedTuple): - """Used to store the test status result category, shortletter and verbose word. - For example ``"rerun", "R", ("RERUN", {"yellow": True})``. - - :ivar category: - The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string. - - :ivar letter: - The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string. - - :ivar word: - Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``, - ``"ERROR"``, or the empty string. - """ - - category: str - letter: str - word: str | tuple[str, Mapping[str, bool]] - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "Reporting", after="general") - group._addoption( # private to use reserved lower-case short option - "-v", - "--verbose", - action="count", - default=0, - dest="verbose", - help="Increase verbosity", - ) - group.addoption( - "--no-header", - action="store_true", - default=False, - dest="no_header", - help="Disable header", - ) - group.addoption( - "--no-summary", - action="store_true", - default=False, - dest="no_summary", - help="Disable summary", - ) - group.addoption( - "--no-fold-skipped", - action="store_false", - dest="fold_skipped", - default=True, - help="Do not fold skipped tests in short summary.", - ) - group.addoption( - "--force-short-summary", - action="store_true", - dest="force_short_summary", - default=False, - help="Force condensed summary output regardless of verbosity level.", - ) - group._addoption( # private to use reserved lower-case short option - "-q", - "--quiet", - action=MoreQuietAction, - default=0, - dest="verbose", - help="Decrease verbosity", - ) - group.addoption( - "--verbosity", - dest="verbose", - type=int, - default=0, - help="Set verbosity. Default: 0.", - ) - group._addoption( # private to use reserved lower-case short option - "-r", - "--report-chars", - action="store", - dest="reportchars", - default=_REPORTCHARS_DEFAULT, - metavar="chars", - help="Show extra test summary info as specified by chars: (f)ailed, " - "(E)rror, (s)kipped, (x)failed, (X)passed, " - "(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. " - "(w)arnings are enabled by default (see --disable-warnings), " - "'N' can be used to reset the list. (default: 'fE').", - ) - group.addoption( - "--disable-warnings", - "--disable-pytest-warnings", - default=False, - dest="disable_warnings", - action="store_true", - help="Disable warnings summary", - ) - group._addoption( # private to use reserved lower-case short option - "-l", - "--showlocals", - action="store_true", - dest="showlocals", - default=False, - help="Show locals in tracebacks (disabled by default)", - ) - group.addoption( - "--no-showlocals", - action="store_false", - dest="showlocals", - help="Hide locals in tracebacks (negate --showlocals passed through addopts)", - ) - group.addoption( - "--tb", - metavar="style", - action="store", - dest="tbstyle", - default="auto", - choices=["auto", "long", "short", "no", "line", "native"], - help="Traceback print mode (auto/long/short/line/native/no)", - ) - group.addoption( - "--xfail-tb", - action="store_true", - dest="xfail_tb", - default=False, - help="Show tracebacks for xfail (as long as --tb != no)", - ) - group.addoption( - "--show-capture", - action="store", - dest="showcapture", - choices=["no", "stdout", "stderr", "log", "all"], - default="all", - help="Controls how captured stdout/stderr/log is shown on failed tests. " - "Default: all.", - ) - group.addoption( - "--fulltrace", - "--full-trace", - action="store_true", - default=False, - help="Don't cut any tracebacks (default is to cut)", - ) - group.addoption( - "--color", - metavar="color", - action="store", - dest="color", - default="auto", - choices=["yes", "no", "auto"], - help="Color terminal output (yes/no/auto)", - ) - group.addoption( - "--code-highlight", - default="yes", - choices=["yes", "no"], - help="Whether code should be highlighted (only if --color is also enabled). " - "Default: yes.", - ) - - parser.addini( - "console_output_style", - help='Console output: "classic", or with additional progress information ' - '("progress" (percentage) | "count" | "progress-even-when-capture-no" (forces ' - "progress even when capture=no)", - default="progress", - ) - Config._add_verbosity_ini( - parser, - Config.VERBOSITY_TEST_CASES, - help=( - "Specify a verbosity level for test case execution, overriding the main level. " - "Higher levels will provide more detailed information about each test case executed." - ), - ) - - -def pytest_configure(config: Config) -> None: - reporter = TerminalReporter(config, sys.stdout) - config.pluginmanager.register(reporter, "terminalreporter") - if config.option.debug or config.option.traceconfig: - - def mywriter(tags, args): - msg = " ".join(map(str, args)) - reporter.write_line("[traceconfig] " + msg) - - config.trace.root.setprocessor("pytest:config", mywriter) - - # See terminalprogress.py. - # On Windows it's safe to load by default. - if sys.platform == "win32": - config.pluginmanager.import_plugin("terminalprogress") - - -def getreportopt(config: Config) -> str: - reportchars: str = config.option.reportchars - - old_aliases = {"F", "S"} - reportopts = "" - for char in reportchars: - if char in old_aliases: - char = char.lower() - if char == "a": - reportopts = "sxXEf" - elif char == "A": - reportopts = "PpsxXEf" - elif char == "N": - reportopts = "" - elif char not in reportopts: - reportopts += char - - if not config.option.disable_warnings and "w" not in reportopts: - reportopts = "w" + reportopts - elif config.option.disable_warnings and "w" in reportopts: - reportopts = reportopts.replace("w", "") - - return reportopts - - -@hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: - letter = "F" - if report.passed: - letter = "." - elif report.skipped: - letter = "s" - - outcome: str = report.outcome - if report.when in ("collect", "setup", "teardown") and outcome == "failed": - outcome = "error" - letter = "E" - - return outcome, letter, outcome.upper() - - -@dataclasses.dataclass -class WarningReport: - """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. - - :ivar str message: - User friendly message about the warning. - :ivar str|None nodeid: - nodeid that generated the warning (see ``get_location``). - :ivar tuple fslocation: - File system location of the source of the warning (see ``get_location``). - """ - - message: str - nodeid: str | None = None - fslocation: tuple[str, int] | None = None - - count_towards_summary: ClassVar = True - - def get_location(self, config: Config) -> str | None: - """Return the more user-friendly information about the location of a warning, or None.""" - if self.nodeid: - return self.nodeid - if self.fslocation: - filename, linenum = self.fslocation - relpath = bestrelpath(config.invocation_params.dir, absolutepath(filename)) - return f"{relpath}:{linenum}" - return None - - -@final -class TerminalReporter: - def __init__(self, config: Config, file: TextIO | None = None) -> None: - import _pytest.config - - self.config = config - self._numcollected = 0 - self._session: Session | None = None - self._showfspath: bool | None = None - - self.stats: dict[str, list[Any]] = {} - self._main_color: str | None = None - self._known_types: list[str] | None = None - self.startpath = config.invocation_params.dir - if file is None: - file = sys.stdout - self._tw = _pytest.config.create_terminal_writer(config, file) - self._screen_width = self._tw.fullwidth - self.currentfspath: None | Path | str | int = None - self.reportchars = getreportopt(config) - self.foldskipped = config.option.fold_skipped - self.hasmarkup = self._tw.hasmarkup - # isatty should be a method but was wrongly implemented as a boolean. - # We use CallableBool here to support both. - self.isatty = compat.CallableBool(file.isatty()) - self._progress_nodeids_reported: set[str] = set() - self._timing_nodeids_reported: set[str] = set() - self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write = timing.Instant() - self._already_displayed_warnings: int | None = None - self._keyboardinterrupt_memo: ExceptionRepr | None = None - - def _determine_show_progress_info( - self, - ) -> Literal["progress", "count", "times", False]: - """Return whether we should display progress information based on the current config.""" - # do not show progress if we are not capturing output (#3038) unless explicitly - # overridden by progress-even-when-capture-no - if ( - self.config.getoption("capture", "no") == "no" - and self.config.getini("console_output_style") - != "progress-even-when-capture-no" - ): - return False - # do not show progress if we are showing fixture setup/teardown - if self.config.getoption("setupshow", False): - return False - cfg: str = self.config.getini("console_output_style") - if cfg in {"progress", "progress-even-when-capture-no"}: - return "progress" - elif cfg == "count": - return "count" - elif cfg == "times": - return "times" - else: - return False - - @property - def verbosity(self) -> int: - verbosity: int = self.config.option.verbose - return verbosity - - @property - def showheader(self) -> bool: - return self.verbosity >= 0 - - @property - def no_header(self) -> bool: - return bool(self.config.option.no_header) - - @property - def no_summary(self) -> bool: - return bool(self.config.option.no_summary) - - @property - def showfspath(self) -> bool: - if self._showfspath is None: - return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0 - return self._showfspath - - @showfspath.setter - def showfspath(self, value: bool | None) -> None: - self._showfspath = value - - @property - def showlongtestinfo(self) -> bool: - return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0 - - @property - def reported_progress(self) -> int: - """The amount of items reported in the progress so far. - - :meta private: - """ - return len(self._progress_nodeids_reported) - - def hasopt(self, char: str) -> bool: - char = {"xfailed": "x", "skipped": "s"}.get(char, char) - return char in self.reportchars - - def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None: - fspath = self.config.rootpath / nodeid.split("::", maxsplit=1)[0] - if self.currentfspath is None or fspath != self.currentfspath: - if self.currentfspath is not None and self._show_progress_info: - self._write_progress_information_filling_space() - self.currentfspath = fspath - relfspath = bestrelpath(self.startpath, fspath) - self._tw.line() - self._tw.write(relfspath + " ") - self._tw.write(res, flush=True, **markup) - - def write_ensure_prefix(self, prefix: str, extra: str = "", **kwargs) -> None: - if self.currentfspath != prefix: - self._tw.line() - self.currentfspath = prefix - self._tw.write(prefix) - if extra: - self._tw.write(extra, **kwargs) - self.currentfspath = -2 - - def ensure_newline(self) -> None: - if self.currentfspath: - self._tw.line() - self.currentfspath = None - - def wrap_write( - self, - content: str, - *, - flush: bool = False, - margin: int = 8, - line_sep: str = "\n", - **markup: bool, - ) -> None: - """Wrap message with margin for progress info.""" - width_of_current_line = self._tw.width_of_current_line - wrapped = line_sep.join( - textwrap.wrap( - " " * width_of_current_line + content, - width=self._screen_width - margin, - drop_whitespace=True, - replace_whitespace=False, - ), - ) - wrapped = wrapped[width_of_current_line:] - self._tw.write(wrapped, flush=flush, **markup) - - def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: - self._tw.write(content, flush=flush, **markup) - - def write_raw(self, content: str, *, flush: bool = False) -> None: - self._tw.write_raw(content, flush=flush) - - def flush(self) -> None: - self._tw.flush() - - def write_line(self, line: str | bytes, **markup: bool) -> None: - if not isinstance(line, str): - line = str(line, errors="replace") - self.ensure_newline() - self._tw.line(line, **markup) - - def rewrite(self, line: str, **markup: bool) -> None: - """Rewinds the terminal cursor to the beginning and writes the given line. - - :param erase: - If True, will also add spaces until the full terminal width to ensure - previous lines are properly erased. - - The rest of the keyword arguments are markup instructions. - """ - erase = markup.pop("erase", False) - if erase: - fill_count = self._tw.fullwidth - len(line) - 1 - fill = " " * fill_count - else: - fill = "" - line = str(line) - self._tw.write("\r" + line + fill, **markup) - - def write_sep( - self, - sep: str, - title: str | None = None, - fullwidth: int | None = None, - **markup: bool, - ) -> None: - self.ensure_newline() - self._tw.sep(sep, title, fullwidth, **markup) - - def section(self, title: str, sep: str = "=", **kw: bool) -> None: - self._tw.sep(sep, title, **kw) - - def line(self, msg: str, **kw: bool) -> None: - self._tw.line(msg, **kw) - - def _add_stats(self, category: str, items: Sequence[Any]) -> None: - set_main_color = category not in self.stats - self.stats.setdefault(category, []).extend(items) - if set_main_color: - self._set_main_color() - - def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool: - for line in str(excrepr).split("\n"): - self.write_line("INTERNALERROR> " + line) - return True - - def pytest_warning_recorded( - self, - warning_message: warnings.WarningMessage, - nodeid: str, - ) -> None: - from _pytest.warnings import warning_record_to_str - - fslocation = warning_message.filename, warning_message.lineno - message = warning_record_to_str(warning_message) - - warning_report = WarningReport( - fslocation=fslocation, message=message, nodeid=nodeid - ) - self._add_stats("warnings", [warning_report]) - - def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: - if self.config.option.traceconfig: - msg = f"PLUGIN registered: {plugin}" - # XXX This event may happen during setup/teardown time - # which unfortunately captures our output here - # which garbles our output if we use self.write_line. - self.write_line(msg) - - def pytest_deselected(self, items: Sequence[Item]) -> None: - self._add_stats("deselected", items) - - def pytest_runtest_logstart( - self, nodeid: str, location: tuple[str, int | None, str] - ) -> None: - fspath, lineno, domain = location - # Ensure that the path is printed before the - # 1st test of a module starts running. - if self.showlongtestinfo: - line = self._locationline(nodeid, fspath, lineno, domain) - self.write_ensure_prefix(line, "") - self.flush() - elif self.showfspath: - self.write_fspath_result(nodeid, "") - self.flush() - - def pytest_runtest_logreport(self, report: TestReport) -> None: - self._tests_ran = True - rep = report - - res = TestShortLogReport( - *self.config.hook.pytest_report_teststatus(report=rep, config=self.config) - ) - category, letter, word = res.category, res.letter, res.word - if not isinstance(word, tuple): - markup = None - else: - word, markup = word - self._add_stats(category, [rep]) - if not letter and not word: - # Probably passed setup/teardown. - return - if markup is None: - was_xfail = hasattr(report, "wasxfail") - if rep.passed and not was_xfail: - markup = {"green": True} - elif rep.passed and was_xfail: - markup = {"yellow": True} - elif rep.failed: - markup = {"red": True} - elif rep.skipped: - markup = {"yellow": True} - else: - markup = {} - self._progress_nodeids_reported.add(rep.nodeid) - if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0: - self._tw.write(letter, **markup) - # When running in xdist, the logreport and logfinish of multiple - # items are interspersed, e.g. `logreport`, `logreport`, - # `logfinish`, `logfinish`. To avoid the "past edge" calculation - # from getting confused and overflowing (#7166), do the past edge - # printing here and not in logfinish, except for the 100% which - # should only be printed after all teardowns are finished. - if self._show_progress_info and not self._is_last_item: - self._write_progress_information_if_past_edge() - else: - line = self._locationline(rep.nodeid, *rep.location) - running_xdist = hasattr(rep, "node") - if not running_xdist: - self.write_ensure_prefix(line, word, **markup) - if rep.skipped or hasattr(report, "wasxfail"): - reason = _get_raw_skip_reason(rep) - if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2: - available_width = ( - (self._tw.fullwidth - self._tw.width_of_current_line) - - len(" [100%]") - - 1 - ) - formatted_reason = _format_trimmed( - " ({})", reason, available_width - ) - else: - formatted_reason = f" ({reason})" - - if reason and formatted_reason is not None: - self.wrap_write(formatted_reason) - if self._show_progress_info: - self._write_progress_information_filling_space() - else: - self.ensure_newline() - self._tw.write(f"[{rep.node.gateway.id}]") - if self._show_progress_info: - self._tw.write( - self._get_progress_information_message() + " ", cyan=True - ) - else: - self._tw.write(" ") - self._tw.write(word, **markup) - self._tw.write(" " + line) - self.currentfspath = -2 - self.flush() - - @property - def _is_last_item(self) -> bool: - assert self._session is not None - return self.reported_progress == self._session.testscollected - - @hookimpl(wrapper=True) - def pytest_runtestloop(self) -> Generator[None, object, object]: - result = yield - - # Write the final/100% progress -- deferred until the loop is complete. - if ( - self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 - and self._show_progress_info - and self.reported_progress - ): - self._write_progress_information_filling_space() - - return result - - def _get_progress_information_message(self) -> str: - assert self._session - collected = self._session.testscollected - if self._show_progress_info == "count": - if collected: - progress = self.reported_progress - counter_format = f"{{:{len(str(collected))}d}}" - format_string = f" [{counter_format}/{{}}]" - return format_string.format(progress, collected) - return f" [ {collected} / {collected} ]" - if self._show_progress_info == "times": - if not collected: - return "" - all_reports = ( - self._get_reports_to_display("passed") - + self._get_reports_to_display("xpassed") - + self._get_reports_to_display("failed") - + self._get_reports_to_display("xfailed") - + self._get_reports_to_display("skipped") - + self._get_reports_to_display("error") - + self._get_reports_to_display("") - ) - current_location = all_reports[-1].location[0] - not_reported = [ - r for r in all_reports if r.nodeid not in self._timing_nodeids_reported - ] - tests_in_module = sum( - i.location[0] == current_location for i in self._session.items - ) - tests_completed = sum( - r.when == "setup" - for r in not_reported - if r.location[0] == current_location - ) - last_in_module = tests_completed == tests_in_module - if self.showlongtestinfo or last_in_module: - self._timing_nodeids_reported.update(r.nodeid for r in not_reported) - return format_node_duration( - sum(r.duration for r in not_reported if isinstance(r, TestReport)) - ) - return "" - if collected: - return f" [{self.reported_progress * 100 // collected:3d}%]" - return " [100%]" - - def _write_progress_information_if_past_edge(self) -> None: - w = self._width_of_current_line - if self._show_progress_info == "count": - assert self._session - num_tests = self._session.testscollected - progress_length = len(f" [{num_tests}/{num_tests}]") - elif self._show_progress_info == "times": - progress_length = len(" 99h 59m") - else: - progress_length = len(" [100%]") - past_edge = w + progress_length + 1 >= self._screen_width - if past_edge: - main_color, _ = self._get_main_color() - msg = self._get_progress_information_message() - self._tw.write(msg + "\n", **{main_color: True}) - - def _write_progress_information_filling_space(self) -> None: - color, _ = self._get_main_color() - msg = self._get_progress_information_message() - w = self._width_of_current_line - fill = self._tw.fullwidth - w - 1 - self.write(msg.rjust(fill), flush=True, **{color: True}) - - @property - def _width_of_current_line(self) -> int: - """Return the width of the current line.""" - return self._tw.width_of_current_line - - def pytest_collection(self) -> None: - if self.isatty(): - if self.config.option.verbose >= 0: - self.write("collecting ... ", flush=True, bold=True) - elif self.config.option.verbose >= 1: - self.write("collecting ... ", flush=True, bold=True) - - def pytest_collectreport(self, report: CollectReport) -> None: - if report.failed: - self._add_stats("error", [report]) - elif report.skipped: - self._add_stats("skipped", [report]) - items = [x for x in report.result if isinstance(x, Item)] - self._numcollected += len(items) - if self.isatty(): - self.report_collect() - - def report_collect(self, final: bool = False) -> None: - if self.config.option.verbose < 0: - return - - if not final: - # Only write the "collecting" report every `REPORT_COLLECTING_RESOLUTION`. - if ( - self._collect_report_last_write.elapsed().seconds - < REPORT_COLLECTING_RESOLUTION - ): - return - self._collect_report_last_write = timing.Instant() - - errors = len(self.stats.get("error", [])) - skipped = len(self.stats.get("skipped", [])) - deselected = len(self.stats.get("deselected", [])) - selected = self._numcollected - deselected - line = "collected " if final else "collecting " - line += ( - str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") - ) - if errors: - line += f" / {errors} error{'s' if errors != 1 else ''}" - if deselected: - line += f" / {deselected} deselected" - if skipped: - line += f" / {skipped} skipped" - if self._numcollected > selected: - line += f" / {selected} selected" - if self.isatty(): - self.rewrite(line, bold=True, erase=True) - if final: - self.write("\n") - else: - self.write_line(line) - - @hookimpl(trylast=True) - def pytest_sessionstart(self, session: Session) -> None: - self._session = session - self._session_start = timing.Instant() - if not self.showheader: - return - self.write_sep("=", "test session starts", bold=True) - verinfo = platform.python_version() - if not self.no_header: - msg = f"platform {sys.platform} -- Python {verinfo}" - pypy_version_info = getattr(sys, "pypy_version_info", None) - if pypy_version_info: - verinfo = ".".join(map(str, pypy_version_info[:3])) - msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]" - msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}" - if ( - self.verbosity > 0 - or self.config.option.debug - or getattr(self.config.option, "pastebin", None) - ): - msg += " -- " + str(sys.executable) - self.write_line(msg) - lines = self.config.hook.pytest_report_header( - config=self.config, start_path=self.startpath - ) - self._write_report_lines_from_hooks(lines) - - def _write_report_lines_from_hooks( - self, lines: Sequence[str | Sequence[str]] - ) -> None: - for line_or_lines in reversed(lines): - if isinstance(line_or_lines, str): - self.write_line(line_or_lines) - else: - for line in line_or_lines: - self.write_line(line) - - def pytest_report_header(self, config: Config) -> list[str]: - result = [f"rootdir: {config.rootpath}"] - - if config.inipath: - warning = "" - if config._ignored_config_files: - warning = f" (WARNING: ignoring pytest config in {', '.join(config._ignored_config_files)}!)" - result.append( - "configfile: " + bestrelpath(config.rootpath, config.inipath) + warning - ) - - if config.args_source == Config.ArgsSource.TESTPATHS: - testpaths: list[str] = config.getini("testpaths") - result.append("testpaths: {}".format(", ".join(testpaths))) - - plugininfo = config.pluginmanager.list_plugin_distinfo() - if plugininfo: - result.append( - "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo))) - ) - return result - - def pytest_collection_finish(self, session: Session) -> None: - self.report_collect(True) - - lines = self.config.hook.pytest_report_collectionfinish( - config=self.config, - start_path=self.startpath, - items=session.items, - ) - self._write_report_lines_from_hooks(lines) - - if self.config.getoption("collectonly"): - if session.items: - if self.config.option.verbose > -1: - self._tw.line("") - self._printcollecteditems(session.items) - - failed = self.stats.get("failed") - if failed: - self._tw.sep("!", "collection failures") - for rep in failed: - rep.toterminal(self._tw) - - def _printcollecteditems(self, items: Sequence[Item]) -> None: - test_cases_verbosity = self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) - if test_cases_verbosity < 0: - if test_cases_verbosity < -1: - counts = Counter(item.nodeid.split("::", 1)[0] for item in items) - for name, count in sorted(counts.items()): - self._tw.line(f"{name}: {count}") - else: - for item in items: - self._tw.line(item.nodeid) - return - stack: list[Node] = [] - indent = "" - for item in items: - needed_collectors = item.listchain()[1:] # strip root node - while stack: - if stack == needed_collectors[: len(stack)]: - break - stack.pop() - for col in needed_collectors[len(stack) :]: - stack.append(col) - indent = (len(stack) - 1) * " " - self._tw.line(f"{indent}{col}") - if test_cases_verbosity >= 1: - obj = getattr(col, "obj", None) - doc = inspect.getdoc(obj) if obj else None - if doc: - for line in doc.splitlines(): - self._tw.line("{}{}".format(indent + " ", line)) - - @hookimpl(wrapper=True) - def pytest_sessionfinish( - self, session: Session, exitstatus: int | ExitCode - ) -> Generator[None]: - result = yield - self._tw.line("") - summary_exit_codes = ( - ExitCode.OK, - ExitCode.TESTS_FAILED, - ExitCode.INTERRUPTED, - ExitCode.USAGE_ERROR, - ExitCode.NO_TESTS_COLLECTED, - ExitCode.MAX_WARNINGS_ERROR, - ) - if exitstatus in summary_exit_codes and not self.no_summary: - self.config.hook.pytest_terminal_summary( - terminalreporter=self, exitstatus=exitstatus, config=self.config - ) - # Check --max-warnings threshold after all warnings have been collected. - max_warnings = self._get_max_warnings() - if max_warnings is not None and session.exitstatus == ExitCode.OK: - warning_count = len(self.stats.get("warnings", [])) - if warning_count > max_warnings: - session.exitstatus = ExitCode.MAX_WARNINGS_ERROR - self.write_line( - "Tests pass, but maximum allowed warnings exceeded: " - f"{warning_count} > {max_warnings}", - red=True, - ) - if session.shouldfail: - self.write_sep("!", str(session.shouldfail), red=True) - if exitstatus == ExitCode.INTERRUPTED: - self._report_keyboardinterrupt() - self._keyboardinterrupt_memo = None - elif session.shouldstop: - self.write_sep("!", str(session.shouldstop), red=True) - self.summary_stats() - return result - - @hookimpl(wrapper=True) - def pytest_terminal_summary(self) -> Generator[None]: - self.summary_errors() - self.summary_failures() - self.summary_xfailures() - self.summary_warnings() - self.summary_passes() - self.summary_xpasses() - try: - return (yield) - finally: - self.short_test_summary() - # Display any extra warnings from teardown here (if any). - self.summary_warnings() - - def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None: - self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) - - def pytest_unconfigure(self) -> None: - if self._keyboardinterrupt_memo is not None: - self._report_keyboardinterrupt() - - def _report_keyboardinterrupt(self) -> None: - excrepr = self._keyboardinterrupt_memo - assert excrepr is not None - assert excrepr.reprcrash is not None - msg = excrepr.reprcrash.message - self.write_sep("!", msg) - if "KeyboardInterrupt" in msg: - if self.config.option.fulltrace: - excrepr.toterminal(self._tw) - else: - excrepr.reprcrash.toterminal(self._tw) - self._tw.line( - "(to show a full traceback on KeyboardInterrupt use --full-trace)", - yellow=True, - ) - - def _locationline( - self, nodeid: str, fspath: str, lineno: int | None, domain: str - ) -> str: - def mkrel(nodeid: str) -> str: - line = self.config.cwd_relative_nodeid(nodeid) - if domain and line.endswith(domain): - line = line[: -len(domain)] - values = domain.split("[") - values[0] = values[0].replace(".", "::") # don't replace '.' in params - line += "[".join(values) - return line - - # fspath comes from testid which has a "/"-normalized path. - if fspath: - res = mkrel(nodeid) - if self.verbosity >= 2 and ( - nodeid.split("::", maxsplit=1)[0] != nodes.norm_sep(fspath) - ): - res += " <- " + bestrelpath(self.startpath, Path(fspath)) - else: - res = "[location]" - return res + " " - - def _getfailureheadline(self, rep): - head_line = rep.head_line - if head_line: - return head_line - return "test session" # XXX? - - def _getcrashline(self, rep): - try: - return str(rep.longrepr.reprcrash) - except AttributeError: - try: - return str(rep.longrepr)[:50] - except AttributeError: - return "" - - def _get_max_warnings(self) -> int | None: - """Return the max_warnings threshold, from CLI or INI, or None if unset.""" - value = self.config.option.max_warnings - if value is not None: - return int(value) - ini_value = self.config.getini("max_warnings") - if ini_value: - return int(ini_value) - return None - - # - # Summaries for sessionfinish. - # - def getreports(self, name: str): - return [x for x in self.stats.get(name, ()) if not hasattr(x, "_pdbshown")] - - def summary_warnings(self) -> None: - if self.hasopt("w"): - all_warnings: list[WarningReport] | None = self.stats.get("warnings") - if not all_warnings: - return - - final = self._already_displayed_warnings is not None - if final: - warning_reports = all_warnings[self._already_displayed_warnings :] - else: - warning_reports = all_warnings - self._already_displayed_warnings = len(warning_reports) - if not warning_reports: - return - - reports_grouped_by_message: dict[str, list[WarningReport]] = {} - for wr in warning_reports: - reports_grouped_by_message.setdefault(wr.message, []).append(wr) - - def collapsed_location_report(reports: list[WarningReport]) -> str: - locations = [] - for w in reports: - location = w.get_location(self.config) - if location: - locations.append(location) - - if len(locations) < 10: - return "\n".join(map(str, locations)) - - counts_by_filename = Counter( - str(loc).split("::", 1)[0] for loc in locations - ) - return "\n".join( - "{}: {} warning{}".format(k, v, "s" if v > 1 else "") - for k, v in counts_by_filename.items() - ) - - title = "warnings summary (final)" if final else "warnings summary" - self.write_sep("=", title, yellow=True, bold=False) - for message, message_reports in reports_grouped_by_message.items(): - maybe_location = collapsed_location_report(message_reports) - if maybe_location: - self._tw.line(maybe_location) - lines = message.splitlines() - indented = "\n".join(" " + x for x in lines) - message = indented.rstrip() - else: - message = message.rstrip() - self._tw.line(message) - self._tw.line() - self._tw.line( - "-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html" - ) - - def summary_passes(self) -> None: - self.summary_passes_combined("passed", "PASSES", "P") - - def summary_xpasses(self) -> None: - self.summary_passes_combined("xpassed", "XPASSES", "X") - - def summary_passes_combined( - self, which_reports: str, sep_title: str, needed_opt: str - ) -> None: - if self.config.option.tbstyle != "no": - if self.hasopt(needed_opt): - reports: list[TestReport] = self.getreports(which_reports) - if not reports: - return - self.write_sep("=", sep_title) - for rep in reports: - if rep.sections: - msg = self._getfailureheadline(rep) - self.write_sep("_", msg, green=True, bold=True) - self._outrep_summary(rep) - self._handle_teardown_sections(rep.nodeid) - - def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: - reports = self.getreports("") - return [ - report - for report in reports - if report.when == "teardown" and report.nodeid == nodeid - ] - - def _handle_teardown_sections(self, nodeid: str) -> None: - for report in self._get_teardown_reports(nodeid): - self.print_teardown_sections(report) - - def print_teardown_sections(self, rep: TestReport) -> None: - showcapture = self.config.option.showcapture - if showcapture == "no": - return - for secname, content in rep.sections: - if showcapture != "all" and showcapture not in secname: - continue - if "teardown" in secname: - self._tw.sep("-", secname) - if content[-1:] == "\n": - content = content[:-1] - self._tw.line(content) - - def summary_failures(self) -> None: - style = self.config.option.tbstyle - self.summary_failures_combined("failed", "FAILURES", style=style) - - def summary_xfailures(self) -> None: - show_tb = self.config.option.xfail_tb - style = self.config.option.tbstyle if show_tb else "no" - self.summary_failures_combined("xfailed", "XFAILURES", style=style) - - def summary_failures_combined( - self, - which_reports: str, - sep_title: str, - *, - style: str, - needed_opt: str | None = None, - ) -> None: - if style != "no": - if not needed_opt or self.hasopt(needed_opt): - reports: list[BaseReport] = self.getreports(which_reports) - if not reports: - return - self.write_sep("=", sep_title) - if style == "line": - for rep in reports: - line = self._getcrashline(rep) - self._outrep_summary(rep) - self.write_line(line) - else: - for rep in reports: - msg = self._getfailureheadline(rep) - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) - self._handle_teardown_sections(rep.nodeid) - - def summary_errors(self) -> None: - if self.config.option.tbstyle != "no": - reports: list[BaseReport] = self.getreports("error") - if not reports: - return - self.write_sep("=", "ERRORS") - for rep in self.stats["error"]: - msg = self._getfailureheadline(rep) - if rep.when == "collect": - msg = "ERROR collecting " + msg - else: - msg = f"ERROR at {rep.when} of {msg}" - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) - - def _outrep_summary(self, rep: BaseReport) -> None: - rep.toterminal(self._tw) - showcapture = self.config.option.showcapture - if showcapture == "no": - return - for secname, content in rep.sections: - if showcapture != "all" and showcapture not in secname: - continue - self._tw.sep("-", secname) - if content[-1:] == "\n": - content = content[:-1] - self._tw.line(content) - - def summary_stats(self) -> None: - if self.verbosity < -1: - return - - session_duration = self._session_start.elapsed() - (parts, main_color) = self.build_summary_stats_line() - line_parts = [] - - display_sep = self.verbosity >= 0 - if display_sep: - fullwidth = self._tw.fullwidth - for text, markup in parts: - with_markup = self._tw.markup(text, **markup) - if display_sep: - fullwidth += len(with_markup) - len(text) - line_parts.append(with_markup) - msg = ", ".join(line_parts) - - main_markup = {main_color: True} - duration = f" in {format_session_duration(session_duration.seconds)}" - duration_with_markup = self._tw.markup(duration, **main_markup) - if display_sep: - fullwidth += len(duration_with_markup) - len(duration) - msg += duration_with_markup - - if display_sep: - markup_for_end_sep = self._tw.markup("", **main_markup) - if markup_for_end_sep.endswith("\x1b[0m"): - markup_for_end_sep = markup_for_end_sep[:-4] - fullwidth += len(markup_for_end_sep) - msg += markup_for_end_sep - - if display_sep: - self.write_sep("=", msg, fullwidth=fullwidth, **main_markup) - else: - self.write_line(msg, **main_markup) - - def short_test_summary(self) -> None: - if not self.reportchars: - return - - def show_simple(lines: list[str], *, stat: str) -> None: - failed = self.stats.get(stat, []) - if not failed: - return - config = self.config - for rep in failed: - color = _color_for_type.get(stat, _color_for_type_default) - line = _get_line_with_reprcrash_message( - config, rep, self._tw, {color: True} - ) - lines.append(line) - - def show_xfailed(lines: list[str]) -> None: - xfailed = self.stats.get("xfailed", []) - for rep in xfailed: - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} - ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) - nodeid = _get_node_id_with_markup(self._tw, self.config, rep) - line = f"{markup_word} {nodeid}" - reason = rep.wasxfail - if reason: - line += " - " + str(reason) - - lines.append(line) - - def show_xpassed(lines: list[str]) -> None: - xpassed = self.stats.get("xpassed", []) - for rep in xpassed: - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} - ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) - nodeid = _get_node_id_with_markup(self._tw, self.config, rep) - line = f"{markup_word} {nodeid}" - reason = rep.wasxfail - if reason: - line += " - " + str(reason) - lines.append(line) - - def show_skipped_folded(lines: list[str]) -> None: - skipped: list[CollectReport] = self.stats.get("skipped", []) - fskips = _folded_skips(self.startpath, skipped) if skipped else [] - if not fskips: - return - verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} - ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) - prefix = "Skipped: " - for num, fspath, lineno, reason in fskips: - if reason.startswith(prefix): - reason = reason[len(prefix) :] - if lineno is not None: - lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}") - else: - lines.append(f"{markup_word} [{num}] {fspath}: {reason}") - - def show_skipped_unfolded(lines: list[str]) -> None: - skipped: list[CollectReport] = self.stats.get("skipped", []) - - for rep in skipped: - assert rep.longrepr is not None - assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr) - assert len(rep.longrepr) == 3, (rep, rep.longrepr) - - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} - ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) - nodeid = _get_node_id_with_markup(self._tw, self.config, rep) - line = f"{markup_word} {nodeid}" - reason = rep.longrepr[2] - if reason: - line += " - " + str(reason) - lines.append(line) - - def show_skipped(lines: list[str]) -> None: - if self.foldskipped: - show_skipped_folded(lines) - else: - show_skipped_unfolded(lines) - - REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { - "x": show_xfailed, - "X": show_xpassed, - "f": partial(show_simple, stat="failed"), - "s": show_skipped, - "p": partial(show_simple, stat="passed"), - "E": partial(show_simple, stat="error"), - } - - lines: list[str] = [] - for char in self.reportchars: - action = REPORTCHAR_ACTIONS.get(char) - if action: # skipping e.g. "P" (passed with output) here. - action(lines) - - if lines: - self.write_sep("=", "short test summary info", cyan=True, bold=True) - for line in lines: - self.write_line(line) - - def _get_main_color(self) -> tuple[str, list[str]]: - if self._main_color is None or self._known_types is None or self._is_last_item: - self._set_main_color() - assert self._main_color - assert self._known_types - return self._main_color, self._known_types - - def _determine_main_color(self, unknown_type_seen: bool) -> str: - stats = self.stats - if "failed" in stats or "error" in stats: - main_color = "red" - elif "warnings" in stats or "xpassed" in stats or unknown_type_seen: - main_color = "yellow" - elif "passed" in stats or not self._is_last_item: - main_color = "green" - else: - main_color = "yellow" - return main_color - - def _set_main_color(self) -> None: - unknown_types: list[str] = [] - for found_type in self.stats: - if found_type: # setup/teardown reports have an empty key, ignore them - if found_type not in KNOWN_TYPES and found_type not in unknown_types: - unknown_types.append(found_type) - self._known_types = list(KNOWN_TYPES) + unknown_types - self._main_color = self._determine_main_color(bool(unknown_types)) - - def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]: - """ - Build the parts used in the last summary stats line. - - The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===". - - This function builds a list of the "parts" that make up for the text in that line, in - the example above it would be:: - - [ - ("12 passed", {"green": True}), - ("2 errors", {"red": True} - ] - - That last dict for each line is a "markup dictionary", used by TerminalWriter to - color output. - - The final color of the line is also determined by this function, and is the second - element of the returned tuple. - """ - if self.config.getoption("collectonly"): - return self._build_collect_only_summary_stats_line() - else: - return self._build_normal_summary_stats_line() - - def _get_reports_to_display(self, key: str) -> list[Any]: - """Get test/collection reports for the given status key, such as `passed` or `error`.""" - reports = self.stats.get(key, []) - return [x for x in reports if getattr(x, "count_towards_summary", True)] - - def _build_normal_summary_stats_line( - self, - ) -> tuple[list[tuple[str, dict[str, bool]]], str]: - main_color, known_types = self._get_main_color() - parts = [] - - for key in known_types: - reports = self._get_reports_to_display(key) - if reports: - count = len(reports) - color = _color_for_type.get(key, _color_for_type_default) - markup = {color: True, "bold": color == main_color} - parts.append(("%d %s" % pluralize(count, key), markup)) # noqa: UP031 - - if not parts: - parts = [("no tests ran", {_color_for_type_default: True})] - - return parts, main_color - - def _build_collect_only_summary_stats_line( - self, - ) -> tuple[list[tuple[str, dict[str, bool]]], str]: - deselected = len(self._get_reports_to_display("deselected")) - errors = len(self._get_reports_to_display("error")) - - if self._numcollected == 0: - parts = [("no tests collected", {"yellow": True})] - main_color = "yellow" - - elif deselected == 0: - main_color = "green" - collected_output = "%d %s collected" % pluralize(self._numcollected, "test") # noqa: UP031 - parts = [(collected_output, {main_color: True})] - else: - all_tests_were_deselected = self._numcollected == deselected - if all_tests_were_deselected: - main_color = "yellow" - collected_output = f"no tests collected ({deselected} deselected)" - else: - main_color = "green" - selected = self._numcollected - deselected - collected_output = f"{selected}/{self._numcollected} tests collected ({deselected} deselected)" - - parts = [(collected_output, {main_color: True})] - - if errors: - main_color = _color_for_type["error"] - parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})] # noqa: UP031 - - return parts, main_color - - -def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport): - nodeid = config.cwd_relative_nodeid(rep.nodeid) - path, *parts = nodeid.split("::") - if parts: - parts_markup = tw.markup("::".join(parts), bold=True) - return path + "::" + parts_markup - else: - return path - - -def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: - """Format msg into format, ellipsizing it if doesn't fit in available_width. - - Returns None if even the ellipsis can't fit. - """ - # Only use the first line. - i = msg.find("\n") - if i != -1: - msg = msg[:i] - - ellipsis = "..." - format_width = wcswidth(format.format("")) - if format_width + len(ellipsis) > available_width: - return None - - if format_width + wcswidth(msg) > available_width: - available_width -= len(ellipsis) - msg = msg[:available_width] - while format_width + wcswidth(msg) > available_width: - msg = msg[:-1] - msg += ellipsis - - return format.format(msg) - - -def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] -) -> str: - """Get summary line for a report, trying to add reprcrash message.""" - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - config, word_markup - ) - word = tw.markup(verbose_word, **verbose_markup) - node = _get_node_id_with_markup(tw, config, rep) - - line = f"{word} {node}" - line_width = wcswidth(line) - - msg: str | None - try: - if isinstance(rep.longrepr, str): - msg = rep.longrepr - else: - # Type ignored intentionally -- possible AttributeError expected. - msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] - except AttributeError: - pass - else: - if ( - running_on_ci() or config.option.verbose >= 2 - ) and not config.option.force_short_summary: - msg = f" - {msg}" - else: - available_width = tw.fullwidth - line_width - msg = _format_trimmed(" - {}", msg, available_width) - if msg is not None: - line += msg - - return line - - -def _folded_skips( - startpath: Path, - skipped: Sequence[CollectReport], -) -> list[tuple[int, str, int | None, str]]: - d: dict[tuple[str, int | None, str], list[CollectReport]] = {} - for event in skipped: - assert event.longrepr is not None - assert isinstance(event.longrepr, tuple), (event, event.longrepr) - assert len(event.longrepr) == 3, (event, event.longrepr) - fspath, lineno, reason = event.longrepr - # For consistency, report all fspaths in relative form. - fspath = bestrelpath(startpath, Path(fspath)) - keywords = getattr(event, "keywords", {}) - # Folding reports with global pytestmark variable. - # This is a workaround, because for now we cannot identify the scope of a skip marker - # TODO: Revisit after marks scope would be fixed. - if ( - event.when == "setup" - and "skip" in keywords - and "pytestmark" not in keywords - ): - key: tuple[str, int | None, str] = (fspath, None, reason) - else: - key = (fspath, lineno, reason) - d.setdefault(key, []).append(event) - values: list[tuple[int, str, int | None, str]] = [] - for key, events in d.items(): - values.append((len(events), *key)) - return values - - -_color_for_type = { - "failed": "red", - "error": "red", - "warnings": "yellow", - "passed": "green", - "subtests passed": "green", - "subtests failed": "red", -} -_color_for_type_default = "yellow" - - -def pluralize(count: int, noun: str) -> tuple[int, str]: - # No need to pluralize words such as `failed` or `passed`. - if noun not in ["error", "warnings", "test"]: - return count, noun - - # The `warnings` key is plural. To avoid API breakage, we keep it that way but - # set it to singular here so we can determine plurality in the same way as we do - # for `error`. - noun = noun.replace("warnings", "warning") - - return count, noun + "s" if count != 1 else noun - - -def _plugin_nameversions(plugininfo) -> list[str]: - values: list[str] = [] - for plugin, dist in plugininfo: - # Gets us name and version! - name = f"{dist.project_name}-{dist.version}" - # Questionable convenience, but it keeps things short. - if name.startswith("pytest-"): - name = name[7:] - # We decided to print python package names they can have more than one plugin. - if name not in values: - values.append(name) - return values - - -def format_session_duration(seconds: float) -> str: - """Format the given seconds in a human readable manner to show in the final summary.""" - if seconds < 60: - return f"{seconds:.2f}s" - else: - dt = datetime.timedelta(seconds=int(seconds)) - return f"{seconds:.2f}s ({dt})" - - -def format_node_duration(seconds: float) -> str: - """Format the given seconds in a human readable manner to show in the test progress.""" - # The formatting is designed to be compact and readable, with at most 7 characters - # for durations below 100 hours. - if seconds < 0.00001: - return f" {seconds * 1000000:.3f}us" - if seconds < 0.0001: - return f" {seconds * 1000000:.2f}us" - if seconds < 0.001: - return f" {seconds * 1000000:.1f}us" - if seconds < 0.01: - return f" {seconds * 1000:.3f}ms" - if seconds < 0.1: - return f" {seconds * 1000:.2f}ms" - if seconds < 1: - return f" {seconds * 1000:.1f}ms" - if seconds < 60: - return f" {seconds:.3f}s" - if seconds < 3600: - return f" {seconds // 60:.0f}m {seconds % 60:.0f}s" - return f" {seconds // 3600:.0f}h {(seconds % 3600) // 60:.0f}m" - - -def _get_raw_skip_reason(report: TestReport) -> str: - """Get the reason string of a skip/xfail/xpass test report. - - The string is just the part given by the user. - """ - if hasattr(report, "wasxfail"): - reason = report.wasxfail - if reason.startswith("reason: "): - reason = reason[len("reason: ") :] - return reason - else: - assert report.skipped - assert isinstance(report.longrepr, tuple) - _, _, reason = report.longrepr - if reason.startswith("Skipped: "): - reason = reason[len("Skipped: ") :] - elif reason == "Skipped": - reason = "" - return reason - - -class TerminalProgressPlugin: - """Terminal progress reporting plugin using OSC 9;4 ANSI sequences. - - Emits OSC 9;4 sequences to indicate test progress to terminal - tabs/windows/etc. - - Not all terminal emulators support this feature. - - Ref: https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC - """ - - def __init__(self, tr: TerminalReporter) -> None: - self._tr = tr - self._session: Session | None = None - self._has_failures = False - - def _emit_progress( - self, - state: Literal["remove", "normal", "error", "indeterminate", "paused"], - progress: int | None = None, - ) -> None: - """Emit OSC 9;4 sequence for indicating progress to the terminal. - - :param state: - Progress state to set. - :param progress: - Progress value 0-100. Required for "normal", optional for "error" - and "paused", otherwise ignored. - """ - assert progress is None or 0 <= progress <= 100 - - # OSC 9;4 sequence: ESC ] 9 ; 4 ; state ; progress ST - # ST can be ESC \ or BEL. ESC \ seems better supported. - match state: - case "remove": - sequence = "\x1b]9;4;0;\x1b\\" - case "normal": - assert progress is not None - sequence = f"\x1b]9;4;1;{progress}\x1b\\" - case "error": - if progress is not None: - sequence = f"\x1b]9;4;2;{progress}\x1b\\" - else: - sequence = "\x1b]9;4;2;\x1b\\" - case "indeterminate": - sequence = "\x1b]9;4;3;\x1b\\" - case "paused": - if progress is not None: - sequence = f"\x1b]9;4;4;{progress}\x1b\\" - else: - sequence = "\x1b]9;4;4;\x1b\\" - - self._tr.write_raw(sequence, flush=True) - - @hookimpl - def pytest_sessionstart(self, session: Session) -> None: - self._session = session - # Show indeterminate progress during collection. - self._emit_progress("indeterminate") - - @hookimpl - def pytest_collection_finish(self) -> None: - assert self._session is not None - if self._session.testscollected > 0: - # Switch from indeterminate to 0% progress. - self._emit_progress("normal", 0) - - @hookimpl - def pytest_runtest_logreport(self, report: TestReport) -> None: - if report.failed: - self._has_failures = True - - # Let's consider the "call" phase for progress. - if report.when != "call": - return - - # Calculate and emit progress. - assert self._session is not None - collected = self._session.testscollected - if collected > 0: - reported = self._tr.reported_progress - progress = min(reported * 100 // collected, 100) - self._emit_progress("error" if self._has_failures else "normal", progress) - - @hookimpl - def pytest_sessionfinish(self) -> None: - self._emit_progress("remove") diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/terminalprogress.py b/tests/venv2/lib/python3.11/site-packages/_pytest/terminalprogress.py deleted file mode 100644 index 287f0d5..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/terminalprogress.py +++ /dev/null @@ -1,30 +0,0 @@ -# A plugin to register the TerminalProgressPlugin plugin. -# -# This plugin is not loaded by default due to compatibility issues (#13896), -# but can be enabled in one of these ways: -# - The terminal plugin enables it in a few cases where it's safe, and not -# blocked by the user (using e.g. `-p no:terminalprogress`). -# - The user explicitly requests it, e.g. using `-p terminalprogress`. -# -# In a few years, if it's safe, we can consider enabling it by default. Then, -# this file will become unnecessary and can be inlined into terminal.py. - -from __future__ import annotations - -import os - -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.terminal import TerminalProgressPlugin -from _pytest.terminal import TerminalReporter - - -@hookimpl(trylast=True) -def pytest_configure(config: Config) -> None: - reporter: TerminalReporter | None = config.pluginmanager.get_plugin( - "terminalreporter" - ) - - if reporter is not None and reporter.isatty() and os.environ.get("TERM") != "dumb": - plugin = TerminalProgressPlugin(reporter) - config.pluginmanager.register(plugin, name="terminalprogress-plugin") diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/threadexception.py b/tests/venv2/lib/python3.11/site-packages/_pytest/threadexception.py deleted file mode 100644 index eb57783..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/threadexception.py +++ /dev/null @@ -1,152 +0,0 @@ -from __future__ import annotations - -import collections -from collections.abc import Callable -import functools -import sys -import threading -import traceback -from typing import NamedTuple -from typing import TYPE_CHECKING -import warnings - -from _pytest.config import Config -from _pytest.nodes import Item -from _pytest.stash import StashKey -from _pytest.tracemalloc import tracemalloc_message -import pytest - - -if TYPE_CHECKING: - pass - -if sys.version_info < (3, 11): - from exceptiongroup import ExceptionGroup - - -class ThreadExceptionMeta(NamedTuple): - msg: str - cause_msg: str - exc_value: BaseException | None - - -thread_exceptions: StashKey[collections.deque[ThreadExceptionMeta | BaseException]] = ( - StashKey() -) - - -def collect_thread_exception(config: Config) -> None: - pop_thread_exception = config.stash[thread_exceptions].pop - errors: list[pytest.PytestUnhandledThreadExceptionWarning | RuntimeError] = [] - meta = None - hook_error = None - try: - while True: - try: - meta = pop_thread_exception() - except IndexError: - break - - if isinstance(meta, BaseException): - hook_error = RuntimeError("Failed to process thread exception") - hook_error.__cause__ = meta - errors.append(hook_error) - continue - - msg = meta.msg - try: - warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) - except pytest.PytestUnhandledThreadExceptionWarning as e: - # This except happens when the warning is treated as an error (e.g. `-Werror`). - if meta.exc_value is not None: - # Exceptions have a better way to show the traceback, but - # warnings do not, so hide the traceback from the msg and - # set the cause so the traceback shows up in the right place. - e.args = (meta.cause_msg,) - e.__cause__ = meta.exc_value - errors.append(e) - - if len(errors) == 1: - raise errors[0] - if errors: - raise ExceptionGroup("multiple thread exception warnings", errors) - finally: - del errors, meta, hook_error - - -def cleanup( - *, config: Config, prev_hook: Callable[[threading.ExceptHookArgs], object] -) -> None: - try: - try: - # We don't join threads here, so exceptions raised from any - # threads still running by the time _threading_atexits joins them - # do not get captured (see #13027). - collect_thread_exception(config) - finally: - threading.excepthook = prev_hook - finally: - del config.stash[thread_exceptions] - - -def thread_exception_hook( - args: threading.ExceptHookArgs, - /, - *, - append: Callable[[ThreadExceptionMeta | BaseException], object], -) -> None: - try: - # we need to compute these strings here as they might change after - # the excepthook finishes and before the metadata object is - # collected by a pytest hook - thread_name = "" if args.thread is None else args.thread.name - summary = f"Exception in thread {thread_name}" - traceback_message = "\n\n" + "".join( - traceback.format_exception( - args.exc_type, - args.exc_value, - args.exc_traceback, - ) - ) - tracemalloc_tb = "\n" + tracemalloc_message(args.thread) - msg = summary + traceback_message + tracemalloc_tb - cause_msg = summary + tracemalloc_tb - - append( - ThreadExceptionMeta( - # Compute these strings here as they might change later - msg=msg, - cause_msg=cause_msg, - exc_value=args.exc_value, - ) - ) - except BaseException as e: - append(e) - # Raising this will cause the exception to be logged twice, once in our - # collect_thread_exception and once by sys.excepthook - # which is fine - this should never happen anyway and if it does - # it should probably be reported as a pytest bug. - raise - - -def pytest_configure(config: Config) -> None: - prev_hook = threading.excepthook - deque: collections.deque[ThreadExceptionMeta | BaseException] = collections.deque() - config.stash[thread_exceptions] = deque - config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook)) - threading.excepthook = functools.partial(thread_exception_hook, append=deque.append) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_setup(item: Item) -> None: - collect_thread_exception(item.config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_call(item: Item) -> None: - collect_thread_exception(item.config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_teardown(item: Item) -> None: - collect_thread_exception(item.config) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/timing.py b/tests/venv2/lib/python3.11/site-packages/_pytest/timing.py deleted file mode 100644 index 639bd99..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/timing.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Indirection for time functions. - -We intentionally grab some "time" functions internally to avoid tests mocking "time" to affect -pytest runtime information (issue #185). - -Fixture "mock_timing" also interacts with this module for pytest's own tests. -""" - -from __future__ import annotations - -import dataclasses -from datetime import datetime -from datetime import timezone -from time import perf_counter -from time import sleep -from time import time -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from pytest import MonkeyPatch - - -@dataclasses.dataclass(frozen=True) -class Instant: - """ - Represents an instant in time, used to both get the timestamp value and to measure - the duration of a time span. - - Inspired by Rust's `std::time::Instant`. - """ - - # Creation time of this instant, using time.time(), to measure actual time. - # Note: using a `lambda` to correctly get the mocked time via `MockTiming`. - # pylint: disable-next=lambda-assignment - time: float = dataclasses.field(default_factory=lambda: time(), init=False) # noqa: PLW0108 - - # Performance counter tick of the instant, used to measure precise elapsed time. - # Note: using a `lambda` to correctly get the mocked time via `MockTiming`. - perf_count: float = dataclasses.field( - default_factory=lambda: perf_counter(), # noqa: PLW0108 - init=False, - ) - - def elapsed(self) -> Duration: - """Measure the duration since `Instant` was created.""" - return Duration(start=self, stop=Instant()) - - def as_utc(self) -> datetime: - """Instant as UTC datetime.""" - return datetime.fromtimestamp(self.time, timezone.utc) - - -@dataclasses.dataclass(frozen=True) -class Duration: - """A span of time as measured by `Instant.elapsed()`.""" - - start: Instant - stop: Instant - - @property - def seconds(self) -> float: - """Elapsed time of the duration in seconds, measured using a performance counter for precise timing.""" - return self.stop.perf_count - self.start.perf_count - - -@dataclasses.dataclass -class MockTiming: - """Mocks _pytest.timing with a known object that can be used to control timing in tests - deterministically. - - pytest itself should always use functions from `_pytest.timing` instead of `time` directly. - - This then allows us more control over time during testing, if testing code also - uses `_pytest.timing` functions. - - Time is static, and only advances through `sleep` calls, thus tests might sleep over large - numbers and obtain accurate time() calls at the end, making tests reliable and instant.""" - - _current_time: float = datetime(2020, 5, 22, 14, 20, 50).timestamp() - - def sleep(self, seconds: float) -> None: - self._current_time += seconds - - def time(self) -> float: - return self._current_time - - def patch(self, monkeypatch: MonkeyPatch) -> None: - # pylint: disable-next=import-self - from _pytest import timing # noqa: PLW0406 - - monkeypatch.setattr(timing, "sleep", self.sleep) - monkeypatch.setattr(timing, "time", self.time) - monkeypatch.setattr(timing, "perf_counter", self.time) - - -__all__ = ["perf_counter", "sleep", "time"] diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/tmpdir.py b/tests/venv2/lib/python3.11/site-packages/_pytest/tmpdir.py deleted file mode 100644 index 9196006..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/tmpdir.py +++ /dev/null @@ -1,351 +0,0 @@ -# mypy: allow-untyped-defs -"""Support for providing temporary directories to test functions.""" - -from __future__ import annotations - -import atexit -from collections.abc import Generator -from contextlib import ExitStack -import dataclasses -import os -from pathlib import Path -import re -from shutil import rmtree -import stat -import tempfile -from typing import Any -from typing import final -from typing import Literal - -from .pathlib import cleanup_dead_symlinks -from .pathlib import LOCK_TIMEOUT -from .pathlib import make_numbered_dir -from .pathlib import make_numbered_dir_with_cleanup -from .pathlib import rm_rf -from _pytest.compat import get_user_id -from _pytest.config import Config -from _pytest.config import ExitCode -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import FixtureRequest -from _pytest.monkeypatch import MonkeyPatch -from _pytest.nodes import Item -from _pytest.reports import TestReport -from _pytest.stash import StashKey - - -tmppath_result_key = StashKey[dict[str, bool]]() -RetentionType = Literal["all", "failed", "none"] - - -@final -@dataclasses.dataclass -class TempPathFactory: - """Factory for temporary directories under the common base temp directory, - as discussed at :ref:`temporary directory location and retention`. - """ - - _given_basetemp: Path | None - # pluggy TagTracerSub, not currently exposed, so Any. - _trace: Any - _basetemp: Path | None - _retention_count: int - _retention_policy: RetentionType - - def __init__( - self, - given_basetemp: Path | None, - retention_count: int, - retention_policy: RetentionType, - trace, - basetemp: Path | None = None, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - if given_basetemp is None: - self._given_basetemp = None - else: - # Use os.path.abspath() to get absolute path instead of resolve() as it - # does not work the same in all platforms (see #4427). - # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). - self._given_basetemp = Path(os.path.abspath(str(given_basetemp))) - self._trace = trace - self._retention_count = retention_count - self._retention_policy = retention_policy - self._basetemp = basetemp - # Register cleanups for session finish. Also called atexit as a last - # resort if sessionfinish for some reason doesn't happen. - self._exit_stack = ExitStack() - - @classmethod - def from_config( - cls, - config: Config, - *, - _ispytest: bool = False, - ) -> TempPathFactory: - """Create a factory according to pytest configuration. - - :meta private: - """ - check_ispytest(_ispytest) - count = int(config.getini("tmp_path_retention_count")) - if count < 0: - raise ValueError( - f"tmp_path_retention_count must be >= 0. Current input: {count}." - ) - - policy = config.getini("tmp_path_retention_policy") - if policy not in ("all", "failed", "none"): - raise ValueError( - f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}." - ) - - return cls( - given_basetemp=config.option.basetemp, - trace=config.trace.get("tmpdir"), - retention_count=count, - retention_policy=policy, - _ispytest=True, - ) - - def _ensure_relative_to_basetemp(self, basename: str) -> str: - basename = os.path.normpath(basename) - if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp(): - raise ValueError(f"{basename} is not a normalized and relative path") - return basename - - def mktemp(self, basename: str, numbered: bool = True) -> Path: - """Create a new temporary directory managed by the factory. - - :param basename: - Directory base name, must be a relative path. - - :param numbered: - If ``True``, ensure the directory is unique by adding a numbered - suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True`` - means that this function will create directories named ``"foo-0"``, - ``"foo-1"``, ``"foo-2"`` and so on. - - :returns: - The path to the new directory. - """ - basename = self._ensure_relative_to_basetemp(basename) - if not numbered: - p = self.getbasetemp().joinpath(basename) - p.mkdir(mode=0o700) - else: - p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700) - self._trace("mktemp", p) - return p - - def getbasetemp(self) -> Path: - """Return the base temporary directory, creating it if needed. - - :returns: - The base temporary directory. - """ - if self._basetemp is not None: - return self._basetemp - - if self._given_basetemp is not None: - basetemp = self._given_basetemp - if basetemp.exists(): - rm_rf(basetemp) - basetemp.mkdir(mode=0o700) - basetemp = basetemp.resolve() - else: - from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") - temproot = Path(from_env or tempfile.gettempdir()).resolve() - user = get_user() or "unknown" - # use a sub-directory in the temproot to speed-up - # make_numbered_dir() call - rootdir = temproot.joinpath(f"pytest-of-{user}") - try: - rootdir.mkdir(mode=0o700, exist_ok=True) - except OSError: - # getuser() likely returned illegal characters for the platform, use unknown back off mechanism - rootdir = temproot.joinpath("pytest-of-unknown") - rootdir.mkdir(mode=0o700, exist_ok=True) - # Because we use exist_ok=True with a predictable name, make sure - # we are the owners, to prevent any funny business (on unix, where - # temproot is usually shared). - # Also, to keep things private, fixup any world-readable temp - # rootdir's permissions. Historically 0o755 was used, so we can't - # just error out on this, at least for a while. - # Don't follow symlinks, otherwise we're open to symlink-swapping - # TOCTOU vulnerability. - # This check makes us vulnerable to a DoS - a user can `mkdir - # /tmp/pytest-of-otheruser` and then `otheruser` will fail this - # check. For now we don't consider it a real problem. otheruser can - # change their TMPDIR or --basetemp, and maybe give the prankster a - # good scolding. - uid = get_user_id() - if uid is not None: - stat_follow_symlinks = ( - False if os.stat in os.supports_follow_symlinks else True - ) - rootdir_stat = rootdir.stat(follow_symlinks=stat_follow_symlinks) - if stat.S_ISLNK(rootdir_stat.st_mode): - raise OSError( - f"The temporary directory {rootdir} is a symbolic link. " - "Fix this and try again." - ) - if rootdir_stat.st_uid != uid: - raise OSError( - f"The temporary directory {rootdir} is not owned by the current user. " - "Fix this and try again." - ) - if (rootdir_stat.st_mode & 0o077) != 0: - chmod_follow_symlinks = ( - False if os.chmod in os.supports_follow_symlinks else True - ) - rootdir.chmod( - rootdir_stat.st_mode & ~0o077, - follow_symlinks=chmod_follow_symlinks, - ) - keep = self._retention_count - if self._retention_policy == "none": - keep = 0 - basetemp = make_numbered_dir_with_cleanup( - prefix="pytest-", - root=rootdir, - keep=keep, - lock_timeout=LOCK_TIMEOUT, - mode=0o700, - register=self._exit_stack.callback, - ) - # Ensure that the cleanup is called on exit (#1120 possibly?). - # But if the exit stack is closed manually (as it normally should), - # unregister the atexit to avoid pile up. - atexit.register(self._exit_stack.close) - self._exit_stack.callback(atexit.unregister, self._exit_stack.close) - assert basetemp is not None, basetemp - self._basetemp = basetemp - self._trace("new basetemp", basetemp) - return basetemp - - -def get_user() -> str | None: - """Return the current user name, or None if getuser() does not work - in the current environment (see #1010).""" - try: - # In some exotic environments, getpass may not be importable. - import getpass - - return getpass.getuser() - except (ImportError, OSError, KeyError): - return None - - -def pytest_configure(config: Config) -> None: - """Create a TempPathFactory and attach it to the config object. - - This is to comply with existing plugins which expect the handler to be - available at pytest_configure time, but ideally should be moved entirely - to the tmp_path_factory session fixture. - """ - mp = MonkeyPatch() - config.add_cleanup(mp.undo) - _tmp_path_factory = TempPathFactory.from_config(config, _ispytest=True) - mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False) - - -def pytest_addoption(parser: Parser) -> None: - parser.addini( - "tmp_path_retention_count", - help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.", - default="3", - # NOTE: Would have been better as an `int` but can't change it now. - type="string", - ) - - parser.addini( - "tmp_path_retention_policy", - help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. " - "(all/failed/none)", - type="string", - default="all", - ) - - -@fixture(scope="session") -def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: - """Return a :class:`pytest.TempPathFactory` instance for the test session.""" - # Set dynamically by pytest_configure() above. - return request.config._tmp_path_factory # type: ignore - - -def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: - name = request.node.name - name = re.sub(r"[\W]", "_", name) - MAXVAL = 30 - name = name[:MAXVAL] - return factory.mktemp(name, numbered=True) - - -@fixture -def tmp_path( - request: FixtureRequest, tmp_path_factory: TempPathFactory -) -> Generator[Path]: - """Return a temporary directory (as :class:`pathlib.Path` object) - which is unique to each test function invocation. - The temporary directory is created as a subdirectory - of the base temporary directory, with configurable retention, - as discussed in :ref:`temporary directory location and retention`. - """ - path = _mk_tmp(request, tmp_path_factory) - yield path - - # Remove the tmpdir if the policy is "failed" and the test passed. - policy = tmp_path_factory._retention_policy - result_dict = request.node.stash[tmppath_result_key] - - if policy == "failed" and result_dict.get("call", True): - # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, - # permissions, etc, in which case we ignore it. - rmtree(path, ignore_errors=True) - - del request.node.stash[tmppath_result_key] - - -def pytest_sessionfinish(session, exitstatus: int | ExitCode): - """After each session, remove base directory if all the tests passed, - the policy is "failed", and the basetemp is not specified by a user. - """ - tmp_path_factory: TempPathFactory = session.config._tmp_path_factory - basetemp = tmp_path_factory._basetemp - if basetemp is None: - return - - policy = tmp_path_factory._retention_policy - if ( - exitstatus == 0 - and policy == "failed" - and tmp_path_factory._given_basetemp is None - ): - if basetemp.is_dir(): - # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, - # permissions, etc, in which case we ignore it. - rmtree(basetemp, ignore_errors=True) - - # Remove dead symlinks. - if basetemp.is_dir(): - cleanup_dead_symlinks(basetemp) - - # Run the numbered dirs and lock file cleanups registered on the ExitStack. - tmp_path_factory._exit_stack.close() - - -@hookimpl(wrapper=True, tryfirst=True) -def pytest_runtest_makereport( - item: Item, call -) -> Generator[None, TestReport, TestReport]: - rep = yield - assert rep.when is not None - empty: dict[str, bool] = {} - item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed - return rep diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/tracemalloc.py b/tests/venv2/lib/python3.11/site-packages/_pytest/tracemalloc.py deleted file mode 100644 index 5d0b198..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/tracemalloc.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - - -def tracemalloc_message(source: object) -> str: - if source is None: - return "" - - try: - import tracemalloc - except ImportError: - return "" - - tb = tracemalloc.get_object_traceback(source) - if tb is not None: - formatted_tb = "\n".join(tb.format()) - # Use a leading new line to better separate the (large) output - # from the traceback to the previous warning text. - return f"\nObject allocated at:\n{formatted_tb}" - # No need for a leading new line. - url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" - return ( - "Enable tracemalloc to get traceback where the object was allocated.\n" - f"See {url} for more info." - ) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/unittest.py b/tests/venv2/lib/python3.11/site-packages/_pytest/unittest.py deleted file mode 100644 index d5286af..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/unittest.py +++ /dev/null @@ -1,653 +0,0 @@ -# mypy: allow-untyped-defs -"""Discover and run std-library "unittest" style tests.""" - -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from enum import auto -from enum import Enum -import inspect -import sys -import traceback -import types -from typing import Any -from typing import TYPE_CHECKING -from unittest import TestCase - -from _pytest import fixtures -import _pytest._code -from _pytest._code import ExceptionInfo -from _pytest.compat import assert_never -from _pytest.compat import is_async_function -from _pytest.config import hookimpl -from _pytest.fixtures import FixtureRequest -from _pytest.monkeypatch import MonkeyPatch -from _pytest.nodes import Collector -from _pytest.nodes import Item -from _pytest.outcomes import exit -from _pytest.outcomes import fail -from _pytest.outcomes import skip -from _pytest.outcomes import xfail -from _pytest.python import Class -from _pytest.python import Function -from _pytest.python import Module -from _pytest.runner import CallInfo -from _pytest.runner import check_interactive_exception -from _pytest.subtests import SubtestContext -from _pytest.subtests import SubtestReport - - -if sys.version_info[:2] < (3, 11): - from exceptiongroup import ExceptionGroup - -if TYPE_CHECKING: - from types import TracebackType - import unittest - - import twisted.trial.unittest - - -_SysExcInfoType = ( - tuple[type[BaseException], BaseException, types.TracebackType] - | tuple[None, None, None] -) - - -def pytest_pycollect_makeitem( - collector: Module | Class, name: str, obj: object -) -> UnitTestCase | None: - try: - # Has unittest been imported? - ut = sys.modules["unittest"] - # Is obj a subclass of unittest.TestCase? - # Type ignored because `ut` is an opaque module. - if not issubclass(obj, ut.TestCase): # type: ignore - return None - except Exception: - return None - # Is obj a concrete class? - # Abstract classes can't be instantiated so no point collecting them. - if inspect.isabstract(obj): - return None - # Yes, so let's collect it. - return UnitTestCase.from_parent(collector, name=name, obj=obj) - - -class UnitTestCase(Class): - # Marker for fixturemanger.getfixtureinfo() - # to declare that our children do not support funcargs. - nofuncargs = True - - def newinstance(self): - # TestCase __init__ takes the method (test) name. The TestCase - # constructor treats the name "runTest" as a special no-op, so it can be - # used when a dummy instance is needed. While unittest.TestCase has a - # default, some subclasses omit the default (#9610), so always supply - # it. - return self.obj("runTest") - - def collect(self) -> Iterable[Item | Collector]: - from unittest import TestLoader - - cls = self.obj - if not getattr(cls, "__test__", True): - return - - skipped = _is_skipped(cls) - if not skipped: - self._register_unittest_setup_method_fixture(cls) - self._register_unittest_setup_class_fixture(cls) - self._register_setup_class_fixture() - else: - self._register_unittest_skip_fixture(cls) - - self.session._fixturemanager.parsefactories( - holder=self.newinstance(), node=self - ) - - loader = TestLoader() - foundsomething = False - for name in loader.getTestCaseNames(self.obj): - x = getattr(self.obj, name) - if not getattr(x, "__test__", True): - continue - yield TestCaseFunction.from_parent(self, name=name) - foundsomething = True - - if not foundsomething: - runtest = getattr(self.obj, "runTest", None) - if runtest is not None: - ut = sys.modules.get("twisted.trial.unittest", None) - if ut is None or runtest != ut.TestCase.runTest: - yield TestCaseFunction.from_parent(self, name="runTest") - - def _register_unittest_setup_class_fixture(self, cls: type) -> None: - """Register an auto-use fixture to invoke setUpClass and - tearDownClass (#517).""" - setup = getattr(cls, "setUpClass", None) - teardown = getattr(cls, "tearDownClass", None) - if setup is None and teardown is None: - return None - cleanup = getattr(cls, "doClassCleanups", lambda: None) - - def process_teardown_exceptions() -> None: - # tearDown_exceptions is a list set in the class containing exc_infos for errors during - # teardown for the class. - exc_infos = getattr(cls, "tearDown_exceptions", None) - if not exc_infos: - return - exceptions = [exc for (_, exc, _) in exc_infos] - # If a single exception, raise it directly as this provides a more readable - # error (hopefully this will improve in #12255). - if len(exceptions) == 1: - raise exceptions[0] - else: - raise ExceptionGroup("Unittest class cleanup errors", exceptions) - - def unittest_setup_class_fixture( - request: FixtureRequest, - ) -> Generator[None]: - cls = request.cls - if _is_skipped(cls): - reason = cls.__unittest_skip_why__ - raise skip.Exception(reason, _use_item_location=True) - if setup is not None: - try: - setup() - # unittest does not call the cleanup function for every BaseException, so we - # follow this here. - except Exception: - cleanup() - process_teardown_exceptions() - raise - yield - try: - if teardown is not None: - teardown() - finally: - cleanup() - process_teardown_exceptions() - - fixtures.register_fixture( - # Use a unique name to speed up lookup. - name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", - func=unittest_setup_class_fixture, - node=self, - scope="class", - autouse=True, - ) - - def _register_unittest_skip_fixture(self, cls: type) -> None: - """Register an auto-use fixture to skip tests for a class decorated - with @unittest.skip or @unittest.skipIf (#13885).""" - - def unittest_skip_fixture(request: FixtureRequest) -> None: - reason = getattr(cls, "__unittest_skip_why__", "") - raise skip.Exception(reason, _use_item_location=True) - - fixtures.register_fixture( - name=f"_unittest_skip_fixture_{cls.__qualname__}", - func=unittest_skip_fixture, - node=self, - scope="class", - autouse=True, - ) - - def _register_unittest_setup_method_fixture(self, cls: type) -> None: - """Register an auto-use fixture to invoke setup_method and - teardown_method (#517).""" - setup = getattr(cls, "setup_method", None) - teardown = getattr(cls, "teardown_method", None) - if setup is None and teardown is None: - return None - - def unittest_setup_method_fixture( - request: FixtureRequest, - ) -> Generator[None]: - self = request.instance - if _is_skipped(self): - reason = self.__unittest_skip_why__ - raise skip.Exception(reason, _use_item_location=True) - if setup is not None: - setup(self, request.function) - yield - if teardown is not None: - teardown(self, request.function) - - fixtures.register_fixture( - # Use a unique name to speed up lookup. - name=f"_unittest_setup_method_fixture_{cls.__qualname__}", - func=unittest_setup_method_fixture, - node=self, - scope="function", - autouse=True, - ) - - -class TestCaseFunction(Function): - nofuncargs = True - failfast = False - _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None - - def _getinstance(self): - assert isinstance(self.parent, UnitTestCase) - return self.parent.obj(self.name) - - # Backward compat for pytest-django; can be removed after pytest-django - # updates + some slack. - @property - def _testcase(self): - return self.instance - - def setup(self) -> None: - # A bound method to be called during teardown() if set (see 'runtest()'). - self._explicit_tearDown: Callable[[], None] | None = None - super().setup() - if sys.version_info < (3, 11): - # A cache of the subTest errors and non-subtest skips in self._outcome. - # Compute and cache these lists once, instead of computing them again and again for each subtest (#13965). - self._cached_errors_and_skips: tuple[list[Any], list[Any]] | None = None - - def teardown(self) -> None: - if self._explicit_tearDown is not None: - self._explicit_tearDown() - self._explicit_tearDown = None - self._obj = None - del self._instance - super().teardown() - - def startTest(self, testcase: unittest.TestCase) -> None: - pass - - def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: - rawexcinfo = _handle_twisted_exc_info(rawexcinfo) - try: - excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info( - rawexcinfo # type: ignore[arg-type] - ) - # Invoke the attributes to trigger storing the traceback - # trial causes some issue there. - _ = excinfo.value - _ = excinfo.traceback - except TypeError: - try: - try: - values = traceback.format_exception(*rawexcinfo) - values.insert( - 0, - "NOTE: Incompatible Exception Representation, " - "displaying natively:\n\n", - ) - fail("".join(values), pytrace=False) - except (fail.Exception, KeyboardInterrupt): - raise - except BaseException: - fail( - "ERROR: Unknown Incompatible Exception " - f"representation:\n{rawexcinfo!r}", - pytrace=False, - ) - except KeyboardInterrupt: - raise - except fail.Exception: - excinfo = _pytest._code.ExceptionInfo.from_current() - self.__dict__.setdefault("_excinfo", []).append(excinfo) - - def addError( - self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType - ) -> None: - try: - if isinstance(rawexcinfo[1], exit.Exception): - exit(rawexcinfo[1].msg) - except TypeError: - pass - self._addexcinfo(rawexcinfo) - - def addFailure( - self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType - ) -> None: - self._addexcinfo(rawexcinfo) - - def addSkip( - self, testcase: unittest.TestCase, reason: str, *, handle_subtests: bool = True - ) -> None: - from unittest.case import _SubTest # type: ignore[attr-defined] - - def add_skip() -> None: - try: - raise skip.Exception(reason, _use_item_location=True) - except skip.Exception: - self._addexcinfo(sys.exc_info()) - - if not handle_subtests: - add_skip() - return - - if isinstance(testcase, _SubTest): - add_skip() - if self._excinfo is not None: - exc_info = self._excinfo[-1] - self.addSubTest(testcase.test_case, testcase, exc_info) - else: - # For python < 3.11: the non-subtest skips have to be added by `add_skip` only after all subtest - # failures are processed by `_addSubTest`: `self.instance._outcome` has no attribute - # `skipped/errors` anymore. - # We also need to check if `self.instance._outcome` is `None` (this happens if the test - # class/method is decorated with `unittest.skip`, see pytest-dev/pytest-subtests#173). - if sys.version_info < (3, 11) and self.instance._outcome is not None: - subtest_errors, _ = self._obtain_errors_and_skips() - if len(subtest_errors) == 0: - add_skip() - else: - add_skip() - - def addExpectedFailure( - self, - testcase: unittest.TestCase, - rawexcinfo: _SysExcInfoType, - reason: str = "", - ) -> None: - try: - xfail(str(reason)) - except xfail.Exception: - self._addexcinfo(sys.exc_info()) - - def addUnexpectedSuccess( - self, - testcase: unittest.TestCase, - reason: twisted.trial.unittest.Todo | None = None, - ) -> None: - msg = "Unexpected success" - if reason: - msg += f": {reason.reason}" - # Preserve unittest behaviour - fail the test. Explicitly not an XPASS. - try: - fail(msg, pytrace=False) - except fail.Exception: - self._addexcinfo(sys.exc_info()) - - def addSuccess(self, testcase: unittest.TestCase) -> None: - pass - - def stopTest(self, testcase: unittest.TestCase) -> None: - pass - - def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: - pass - - def runtest(self) -> None: - from _pytest.debugging import maybe_wrap_pytest_function_for_tracing - - testcase = self.instance - assert testcase is not None - - maybe_wrap_pytest_function_for_tracing(self) - - # Let the unittest framework handle async functions. - if is_async_function(self.obj): - testcase(result=self) - else: - # When --pdb is given, we want to postpone calling tearDown() otherwise - # when entering the pdb prompt, tearDown() would have probably cleaned up - # instance variables, which makes it difficult to debug. - # Arguably we could always postpone tearDown(), but this changes the moment where the - # TestCase instance interacts with the results object, so better to only do it - # when absolutely needed. - # We need to consider if the test itself is skipped, or the whole class. - assert isinstance(self.parent, UnitTestCase) - skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj) - if self.config.getoption("usepdb") and not skipped: - self._explicit_tearDown = testcase.tearDown - setattr(testcase, "tearDown", lambda *args: None) - - # We need to update the actual bound method with self.obj, because - # wrap_pytest_function_for_tracing replaces self.obj by a wrapper. - setattr(testcase, self.name, self.obj) - try: - testcase(result=self) - finally: - delattr(testcase, self.name) - - def _traceback_filter( - self, excinfo: _pytest._code.ExceptionInfo[BaseException] - ) -> _pytest._code.Traceback: - traceback = super()._traceback_filter(excinfo) - ntraceback = traceback.filter( - lambda x: not x.frame.f_globals.get("__unittest"), - ) - if not ntraceback: - ntraceback = traceback - return ntraceback - - def addSubTest( - self, - test_case: Any, - test: TestCase, - exc_info: ExceptionInfo[BaseException] - | tuple[type[BaseException], BaseException, TracebackType] - | None, - ) -> None: - # Importing this private symbol locally in case this symbol is renamed/removed in the future; importing - # it globally would break pytest entirely, importing it locally only will break unittests using `addSubTest`. - from unittest.case import _subtest_msg_sentinel # type: ignore[attr-defined] - - exception_info: ExceptionInfo[BaseException] | None - match exc_info: - case tuple(): - exception_info = ExceptionInfo(exc_info, _ispytest=True) - case ExceptionInfo() | None: - exception_info = exc_info - case unreachable: - assert_never(unreachable) - - call_info = CallInfo[None]( - None, - exception_info, - start=0, - stop=0, - duration=0, - when="call", - _ispytest=True, - ) - msg = None if test._message is _subtest_msg_sentinel else str(test._message) # type: ignore[attr-defined] - report = self.ihook.pytest_runtest_makereport(item=self, call=call_info) - sub_report = SubtestReport._new( - report, - SubtestContext(msg=msg, kwargs=dict(test.params)), # type: ignore[attr-defined] - captured_output=None, - captured_logs=None, - ) - self.ihook.pytest_runtest_logreport(report=sub_report) - if check_interactive_exception(call_info, sub_report): - self.ihook.pytest_exception_interact( - node=self, call=call_info, report=sub_report - ) - - # For python < 3.11: add non-subtest skips once all subtest failures are processed by # `_addSubTest`. - if sys.version_info < (3, 11): - subtest_errors, non_subtest_skip = self._obtain_errors_and_skips() - - # Check if we have non-subtest skips: if there are also sub failures, non-subtest skips are not treated in - # `_addSubTest` and have to be added using `add_skip` after all subtest failures are processed. - if len(non_subtest_skip) > 0 and len(subtest_errors) > 0: - # Make sure we have processed the last subtest failure - last_subset_error = subtest_errors[-1] - if exc_info is last_subset_error[-1]: - # Add non-subtest skips (as they could not be treated in `_addSkip`) - for testcase, reason in non_subtest_skip: - self.addSkip(testcase, reason, handle_subtests=False) - - def _obtain_errors_and_skips(self) -> tuple[list[Any], list[Any]]: - """Compute or obtain the cached values for subtest errors and non-subtest skips.""" - from unittest.case import _SubTest # type: ignore[attr-defined] - - assert sys.version_info < (3, 11), ( - "This workaround only should be used in Python 3.10" - ) - if self._cached_errors_and_skips is not None: - return self._cached_errors_and_skips - - subtest_errors = [ - (x, y) - for x, y in self.instance._outcome.errors - if isinstance(x, _SubTest) and y is not None - ] - - non_subtest_skips = [ - (x, y) - for x, y in self.instance._outcome.skipped - if not isinstance(x, _SubTest) - ] - self._cached_errors_and_skips = (subtest_errors, non_subtest_skips) - return subtest_errors, non_subtest_skips - - -@hookimpl(tryfirst=True) -def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: - if isinstance(item, TestCaseFunction): - if item._excinfo: - call.excinfo = item._excinfo.pop(0) - try: - del call.result - except AttributeError: - pass - - # Convert unittest.SkipTest to pytest.skip. - # This covers explicit `raise unittest.SkipTest`. - unittest = sys.modules.get("unittest") - if unittest and call.excinfo and isinstance(call.excinfo.value, unittest.SkipTest): - excinfo = call.excinfo - call2 = CallInfo[None].from_call(lambda: skip(str(excinfo.value)), call.when) - call.excinfo = call2.excinfo - - -def _is_skipped(obj) -> bool: - """Return True if the given object has been marked with @unittest.skip.""" - return bool(getattr(obj, "__unittest_skip__", False)) - - -def pytest_configure() -> None: - """Register the TestCaseFunction class as an IReporter if twisted.trial is available.""" - if _get_twisted_version() is not TwistedVersion.NotInstalled: - from twisted.trial.itrial import IReporter - from zope.interface import classImplements - - classImplements(TestCaseFunction, IReporter) - - -class TwistedVersion(Enum): - """ - The Twisted version installed in the environment. - - We have different workarounds in place for different versions of Twisted. - """ - - # Twisted version 24 or prior. - Version24 = auto() - # Twisted version 25 or later. - Version25 = auto() - # Twisted version is not available. - NotInstalled = auto() - - -def _get_twisted_version() -> TwistedVersion: - # We need to check if "twisted.trial.unittest" is specifically present in sys.modules. - # This is because we intend to integrate with Trial only when it's actively running - # the test suite, but not needed when only other Twisted components are in use. - if "twisted.trial.unittest" not in sys.modules: - return TwistedVersion.NotInstalled - - import importlib.metadata - - import packaging.version - - version_str = importlib.metadata.version("twisted") - version = packaging.version.parse(version_str) - if version.major <= 24: - return TwistedVersion.Version24 - else: - return TwistedVersion.Version25 - - -# Name of the attribute in `twisted.python.Failure` instances that stores -# the `sys.exc_info()` tuple. -# See twisted.trial support in `pytest_runtest_protocol`. -TWISTED_RAW_EXCINFO_ATTR = "_twisted_raw_excinfo" - - -@hookimpl(wrapper=True) -def pytest_runtest_protocol(item: Item) -> Iterator[None]: - if _get_twisted_version() is TwistedVersion.Version24: - import twisted.python.failure as ut - - # Monkeypatch `Failure.__init__` to store the raw exception info. - original__init__ = ut.Failure.__init__ - - def store_raw_exception_info( - self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None - ): # pragma: no cover - if exc_value is None: - raw_exc_info = sys.exc_info() - else: - if exc_type is None: - exc_type = type(exc_value) - if exc_tb is None: - exc_tb = sys.exc_info()[2] - raw_exc_info = (exc_type, exc_value, exc_tb) - setattr(self, TWISTED_RAW_EXCINFO_ATTR, tuple(raw_exc_info)) - try: - original__init__( - self, exc_value, exc_type, exc_tb, captureVars=captureVars - ) - except TypeError: # pragma: no cover - original__init__(self, exc_value, exc_type, exc_tb) - - with MonkeyPatch.context() as patcher: - patcher.setattr(ut.Failure, "__init__", store_raw_exception_info) - return (yield) - else: - return (yield) - - -def _handle_twisted_exc_info( - rawexcinfo: _SysExcInfoType | BaseException, -) -> _SysExcInfoType: - """ - Twisted passes a custom Failure instance to `addError()` instead of using `sys.exc_info()`. - Therefore, if `rawexcinfo` is a `Failure` instance, convert it into the equivalent `sys.exc_info()` tuple - as expected by pytest. - """ - twisted_version = _get_twisted_version() - if twisted_version is TwistedVersion.NotInstalled: - # Unfortunately, because we cannot import `twisted.python.failure` at the top of the file - # and use it in the signature, we need to use `type:ignore` here because we cannot narrow - # the type properly in the `if` statement above. - return rawexcinfo # type:ignore[return-value] - elif twisted_version is TwistedVersion.Version24: - # Twisted calls addError() passing its own classes (like `twisted.python.Failure`), which violates - # the `addError()` signature, so we extract the original `sys.exc_info()` tuple which is stored - # in the object. - if hasattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR): - saved_exc_info = getattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR) - # Delete the attribute from the original object to avoid leaks. - delattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR) - return saved_exc_info # type:ignore[no-any-return] - return rawexcinfo # type:ignore[return-value] - elif twisted_version is TwistedVersion.Version25: - if isinstance(rawexcinfo, BaseException): - import twisted.python.failure - - if isinstance(rawexcinfo, twisted.python.failure.Failure): - tb = rawexcinfo.__traceback__ - if tb is None: - tb = sys.exc_info()[2] - return type(rawexcinfo.value), rawexcinfo.value, tb - - return rawexcinfo # type:ignore[return-value] - else: - # Ideally we would use assert_never() here, but it is not available in all Python versions - # we support, plus we do not require `type_extensions` currently. - assert False, f"Unexpected Twisted version: {twisted_version}" diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/unraisableexception.py b/tests/venv2/lib/python3.11/site-packages/_pytest/unraisableexception.py deleted file mode 100644 index 6c092fb..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/unraisableexception.py +++ /dev/null @@ -1,188 +0,0 @@ -from __future__ import annotations - -import collections -from collections.abc import Callable -import functools -import gc -import sys -import traceback -from typing import NamedTuple -from typing import TYPE_CHECKING -import warnings - -from _pytest.config import Config -from _pytest.nodes import Item -from _pytest.stash import StashKey -from _pytest.tracemalloc import tracemalloc_message -import pytest - - -if TYPE_CHECKING: - pass - -if sys.version_info < (3, 11): - from exceptiongroup import ExceptionGroup - - -# This is a stash item and not a simple constant to allow pytester to override it. -gc_collect_iterations_key = StashKey[int]() - - -def gc_collect_harder(iterations: int) -> None: - for _ in range(iterations): - gc.collect() - - -class UnraisableMeta(NamedTuple): - msg: str - cause_msg: str - exc_value: BaseException | None - - -unraisable_exceptions: StashKey[collections.deque[UnraisableMeta | BaseException]] = ( - StashKey() -) - - -def collect_unraisable(config: Config) -> None: - pop_unraisable = config.stash[unraisable_exceptions].pop - errors: list[pytest.PytestUnraisableExceptionWarning | RuntimeError] = [] - meta = None - hook_error = None - try: - while True: - try: - meta = pop_unraisable() - except IndexError: - break - - if isinstance(meta, BaseException): - hook_error = RuntimeError("Failed to process unraisable exception") - hook_error.__cause__ = meta - errors.append(hook_error) - continue - - msg = meta.msg - try: - warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) - except pytest.PytestUnraisableExceptionWarning as e: - # This except happens when the warning is treated as an error (e.g. `-Werror`). - if meta.exc_value is not None: - # Exceptions have a better way to show the traceback, but - # warnings do not, so hide the traceback from the msg and - # set the cause so the traceback shows up in the right place. - e.args = (meta.cause_msg,) - e.__cause__ = meta.exc_value - errors.append(e) - - if len(errors) == 1: - raise errors[0] - if errors: - raise ExceptionGroup("multiple unraisable exception warnings", errors) - finally: - del errors, meta, hook_error - - -def cleanup( - *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object] -) -> None: - # On PyPy, objects (e.g. coroutines) can survive GC rounds because executing - # their __del__ can resurrect them. The Trio project determined experimentally - # that 5 passes are needed on PyPy to flush everything. On CPython, reference - # counting handles most cleanup immediately, so 1 pass is sufficient. - _default_gc_collect_iterations = 5 if sys.implementation.name == "pypy" else 1 - gc_collect_iterations = config.stash.get( - gc_collect_iterations_key, _default_gc_collect_iterations - ) - try: - try: - gc_collect_harder(gc_collect_iterations) - collect_unraisable(config) - finally: - sys.unraisablehook = prev_hook - finally: - del config.stash[unraisable_exceptions] - - -def unraisable_hook( - unraisable: sys.UnraisableHookArgs, - /, - *, - append: Callable[[UnraisableMeta | BaseException], object], -) -> None: - try: - # we need to compute these strings here as they might change after - # the unraisablehook finishes and before the metadata object is - # collected by a pytest hook - err_msg = ( - "Exception ignored in" if unraisable.err_msg is None else unraisable.err_msg - ) - summary = f"{err_msg}: {unraisable.object!r}" - traceback_message = "\n\n" + "".join( - traceback.format_exception( - unraisable.exc_type, - unraisable.exc_value, - unraisable.exc_traceback, - ) - ) - tracemalloc_tb = "\n" + tracemalloc_message(unraisable.object) - msg = summary + traceback_message + tracemalloc_tb - cause_msg = summary + tracemalloc_tb - - append( - UnraisableMeta( - msg=msg, - cause_msg=cause_msg, - exc_value=unraisable.exc_value, - ) - ) - except BaseException as e: - append(e) - # Raising this will cause the exception to be logged twice, once in our - # collect_unraisable and once by the unraisablehook calling machinery - # which is fine - this should never happen anyway and if it does - # it should probably be reported as a pytest bug. - raise - - -def pytest_configure(config: Config) -> None: - prev_hook = sys.unraisablehook - deque: collections.deque[UnraisableMeta | BaseException] = collections.deque() - config.stash[unraisable_exceptions] = deque - config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook)) - sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append) - - -def pytest_unconfigure(config: Config) -> None: - # Runs before ``_cleanup_stack.close()``, so warning filters from - # cleanup-stack-managed contexts (notably the ``warnings`` plugin's - # ``catch_warnings``) are still installed when garbage-collected - # finalizers fire. A ``config.add_cleanup`` callback would instead - # couple correctness to LIFO pop order across plugins' cleanups. - if unraisable_exceptions not in config.stash: - # ``pytest_configure`` did not complete (e.g. a usage error raised - # in another plugin's configure), so the queue stash was never set. - return - # PyPy can resurrect objects in __del__, so it needs several GC passes - # (5, per the Trio project); CPython frees cycles in one pass. See #14441. - _default_gc_collect_iterations = 5 if sys.implementation.name == "pypy" else 1 - gc_collect_iterations = config.stash.get( - gc_collect_iterations_key, _default_gc_collect_iterations - ) - gc_collect_harder(gc_collect_iterations) - collect_unraisable(config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_setup(item: Item) -> None: - collect_unraisable(item.config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_call(item: Item) -> None: - collect_unraisable(item.config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_teardown(item: Item) -> None: - collect_unraisable(item.config) diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/warning_types.py b/tests/venv2/lib/python3.11/site-packages/_pytest/warning_types.py deleted file mode 100644 index 8fd62ff..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/warning_types.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import annotations - -import dataclasses -import inspect -from types import FunctionType -from typing import Any -from typing import final -from typing import Generic -from typing import TypeVar -import warnings - - -class PytestWarning(UserWarning): - """Base class for all warnings emitted by pytest.""" - - __module__ = "pytest" - - -@final -class PytestAssertRewriteWarning(PytestWarning): - """Warning emitted by the pytest assert rewrite module.""" - - __module__ = "pytest" - - -@final -class PytestCacheWarning(PytestWarning): - """Warning emitted by the cache plugin in various situations.""" - - __module__ = "pytest" - - -@final -class PytestConfigWarning(PytestWarning): - """Warning emitted for configuration issues.""" - - __module__ = "pytest" - - -@final -class PytestCollectionWarning(PytestWarning): - """Warning emitted when pytest is not able to collect a file or symbol in a module.""" - - __module__ = "pytest" - - -class PytestDeprecationWarning(PytestWarning, DeprecationWarning): - """Warning class for features that will be removed in a future version.""" - - __module__ = "pytest" - - -class PytestRemovedIn10Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 10.""" - - __module__ = "pytest" - - -@final -class PytestExperimentalApiWarning(PytestWarning, FutureWarning): - """Warning category used to denote experiments in pytest. - - Use sparingly as the API might change or even be removed completely in a - future version. - """ - - __module__ = "pytest" - - @classmethod - def simple(cls, apiname: str) -> PytestExperimentalApiWarning: - return cls(f"{apiname} is an experimental api that may change over time") - - -@final -class PytestReturnNotNoneWarning(PytestWarning): - """ - Warning emitted when a test function returns a value other than ``None``. - - See :ref:`return-not-none` for details. - """ - - __module__ = "pytest" - - -@final -class PytestUnknownMarkWarning(PytestWarning): - """Warning emitted on use of unknown markers. - - See :ref:`mark` for details. - """ - - __module__ = "pytest" - - -@final -class PytestUnraisableExceptionWarning(PytestWarning): - """An unraisable exception was reported. - - Unraisable exceptions are exceptions raised in :meth:`__del__ ` - implementations and similar situations when the exception cannot be raised - as normal. - """ - - __module__ = "pytest" - - -@final -class PytestUnhandledThreadExceptionWarning(PytestWarning): - """An unhandled exception occurred in a :class:`~threading.Thread`. - - Such exceptions don't propagate normally. - """ - - __module__ = "pytest" - - -_W = TypeVar("_W", bound=PytestWarning) - - -@final -@dataclasses.dataclass -class UnformattedWarning(Generic[_W]): - """A warning meant to be formatted during runtime. - - This is used to hold warnings that need to format their message at runtime, - as opposed to a direct message. - """ - - category: type[_W] - template: str - - def format(self, **kwargs: Any) -> _W: - """Return an instance of the warning category, formatted with given kwargs.""" - return self.category(self.template.format(**kwargs)) - - -@final -class PytestFDWarning(PytestWarning): - """When the lsof plugin finds leaked fds.""" - - __module__ = "pytest" - - -def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: - """ - Issue the warning :param:`message` for the definition of the given :param:`method` - - this helps to log warnings for functions defined prior to finding an issue with them - (like hook wrappers being marked in a legacy mechanism) - """ - lineno = method.__code__.co_firstlineno - filename = inspect.getfile(method) - module = method.__module__ - mod_globals = method.__globals__ - try: - warnings.warn_explicit( - message, - type(message), - filename=filename, - module=module, - registry=mod_globals.setdefault("__warningregistry__", {}), - lineno=lineno, - ) - except Warning as w: - # If warnings are errors (e.g. -Werror), location information gets lost, so we add it to the message. - raise type(w)(f"{w}\n at {filename}:{lineno}") from None diff --git a/tests/venv2/lib/python3.11/site-packages/_pytest/warnings.py b/tests/venv2/lib/python3.11/site-packages/_pytest/warnings.py deleted file mode 100644 index d599d6c..0000000 --- a/tests/venv2/lib/python3.11/site-packages/_pytest/warnings.py +++ /dev/null @@ -1,152 +0,0 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -from collections.abc import Generator -from contextlib import contextmanager -from contextlib import ExitStack -import sys -from typing import Literal -import warnings - -from _pytest.config import apply_warning_filters -from _pytest.config import Config -from _pytest.config import parse_warning_filter -from _pytest.main import Session -from _pytest.nodes import Item -from _pytest.terminal import TerminalReporter -from _pytest.tracemalloc import tracemalloc_message -import pytest - - -@contextmanager -def catch_warnings_for_item( - config: Config, - ihook, - when: Literal["config", "collect", "runtest"], - item: Item | None, - *, - record: bool = True, -) -> Generator[None]: - """Context manager that catches warnings generated in the contained execution block. - - ``item`` can be None if we are not in the context of an item execution. - - Each warning captured triggers the ``pytest_warning_recorded`` hook. - """ - config_filters = config.getini("filterwarnings") - cmdline_filters = config.known_args_namespace.pythonwarnings or [] - with warnings.catch_warnings(record=record) as log: - if not sys.warnoptions: - # If user is not explicitly configuring warning filters, show deprecation warnings by default (#2908). - warnings.filterwarnings("always", category=DeprecationWarning) - warnings.filterwarnings("always", category=PendingDeprecationWarning) - - # To be enabled in pytest 10.0.0. - # warnings.filterwarnings("error", category=pytest.PytestRemovedIn10Warning) - - apply_warning_filters(config_filters, cmdline_filters) - - # apply filters from "filterwarnings" marks - nodeid = "" if item is None else item.nodeid - if item is not None: - for mark in item.iter_markers(name="filterwarnings"): - for arg in mark.args: - warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) - - try: - yield - finally: - if record: - # mypy can't infer that record=True means log is not None; help it. - assert log is not None - - for warning_message in log: - ihook.pytest_warning_recorded.call_historic( - kwargs=dict( - warning_message=warning_message, - nodeid=nodeid, - when=when, - location=None, - ) - ) - - -def warning_record_to_str(warning_message: warnings.WarningMessage) -> str: - """Convert a warnings.WarningMessage to a string.""" - return warnings.formatwarning( - str(warning_message.message), - warning_message.category, - warning_message.filename, - warning_message.lineno, - warning_message.line, - ) + tracemalloc_message(warning_message.source) - - -@pytest.hookimpl(wrapper=True, tryfirst=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: - with catch_warnings_for_item( - config=item.config, ihook=item.ihook, when="runtest", item=item - ): - return (yield) - - -@pytest.hookimpl(wrapper=True, tryfirst=True) -def pytest_collection(session: Session) -> Generator[None, object, object]: - config = session.config - with catch_warnings_for_item( - config=config, ihook=config.hook, when="collect", item=None - ): - return (yield) - - -@pytest.hookimpl(wrapper=True) -def pytest_terminal_summary( - terminalreporter: TerminalReporter, -) -> Generator[None]: - config = terminalreporter.config - with catch_warnings_for_item( - config=config, ihook=config.hook, when="config", item=None - ): - return (yield) - - -@pytest.hookimpl(wrapper=True) -def pytest_sessionfinish(session: Session) -> Generator[None]: - config = session.config - with catch_warnings_for_item( - config=config, ihook=config.hook, when="config", item=None - ): - return (yield) - - -@pytest.hookimpl(wrapper=True) -def pytest_load_initial_conftests( - early_config: Config, -) -> Generator[None]: - with catch_warnings_for_item( - config=early_config, ihook=early_config.hook, when="config", item=None - ): - return (yield) - - -def pytest_configure(config: Config) -> None: - with ExitStack() as stack: - stack.enter_context( - catch_warnings_for_item( - config=config, - ihook=config.hook, - when="config", - item=None, - # this disables recording because the terminalreporter has - # finished by the time it comes to reporting logged warnings - # from the end of config cleanup. So for now, this is only - # useful for setting a warning filter with an 'error' action. - record=False, - ) - ) - config.addinivalue_line( - "markers", - "filterwarnings(warning): add a warning filter to the given test. " - "see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings ", - ) - config.add_cleanup(stack.pop_all().close) diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/PKG-INFO b/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/PKG-INFO deleted file mode 100644 index 9c03a4d..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/PKG-INFO +++ /dev/null @@ -1,66 +0,0 @@ -Metadata-Version: 2.1 -Name: aioopenssl -Version: 0.6.0 -Summary: TLS-capable transport using OpenSSL for asyncio -Home-page: https://github.com/horazont/aioopenssl -Author: Jonas Wielicki -Author-email: jonas@wielicki.name -License: Apache 2.0 -Keywords: openssl asyncio library transport starttls -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Operating System :: POSIX -Classifier: License :: OSI Approved :: Apache Software License -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Topic :: Communications :: Chat -License-File: COPYING - -OpenSSL Transport for asyncio -############################# - -.. image:: https://github.com/horazont/aioopenssl/workflows/CI/badge.svg - :target: https://github.com/horazont/aioopenssl/actions?query=workflow%3ACI+branch%3Adevel - -.. image:: https://coveralls.io/repos/github/horazont/aioopenssl/badge.svg?branch=devel - :target: https://coveralls.io/github/horazont/aioopenssl?branch=devel - -``aioopenssl`` provides a `asyncio -`_ Transport which uses -`PyOpenSSL `_ instead of the built-in ssl -module. - -The transport has two main advantages compared to the original: - -* The TLS handshake can be deferred by passing ``use_starttls=True`` and later - calling the ``starttls()`` coroutine method. - - This is useful for protocols with a `STARTTLS - `_ feature. - -* A coroutine can be called during the TLS handshake; this can be used to defer - the certificate check to a later point, allowing e.g. to get user feedback - before the ``starttls()`` method returns. - - This allows to ask users for certificate trust without the application layer - protocol interfering or starting to communicate with the unverified peer. - -.. note:: - - Use this module at your own risk. It has lower test coverage than I’d like - it to have; it has been exported from aioxmpp on request, where it undergoes - implicit testing. If you find bugs, please report them. If possible, add - regression tests while you’re at it. - - If you find security-critical bugs, please follow the procedure announced in - the `aioxmpp readme `_.` - -Documentation -------------- - -Official documentation can be built with sphinx and is available online -`on our servers `_. diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/SOURCES.txt b/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/SOURCES.txt deleted file mode 100644 index 133cbae..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/SOURCES.txt +++ /dev/null @@ -1,13 +0,0 @@ -COPYING -MANIFEST.in -README.rst -setup.cfg -setup.py -aioopenssl/__init__.py -aioopenssl/utils.py -aioopenssl/version.py -aioopenssl.egg-info/PKG-INFO -aioopenssl.egg-info/SOURCES.txt -aioopenssl.egg-info/dependency_links.txt -aioopenssl.egg-info/requires.txt -aioopenssl.egg-info/top_level.txt \ No newline at end of file diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/dependency_links.txt b/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/installed-files.txt b/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/installed-files.txt deleted file mode 100644 index a1d8bfd..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/installed-files.txt +++ /dev/null @@ -1,11 +0,0 @@ -../aioopenssl/__init__.py -../aioopenssl/__pycache__/__init__.cpython-311.pyc -../aioopenssl/__pycache__/utils.cpython-311.pyc -../aioopenssl/__pycache__/version.cpython-311.pyc -../aioopenssl/utils.py -../aioopenssl/version.py -PKG-INFO -SOURCES.txt -dependency_links.txt -requires.txt -top_level.txt diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/requires.txt b/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/requires.txt deleted file mode 100644 index 9292484..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/requires.txt +++ /dev/null @@ -1 +0,0 @@ -PyOpenSSL diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/top_level.txt b/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/top_level.txt deleted file mode 100644 index 167a190..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl-0.6.0.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -aioopenssl diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioopenssl/__init__.py deleted file mode 100644 index 60ede64..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__init__.py +++ /dev/null @@ -1,901 +0,0 @@ -""" # NOQA -:mod:`aioopenssl` --- A transport for asyncio using :mod:`OpenSSL` -################################################################## - -This package provides a socket-based :class:`asyncio.Transport` which uses -:mod:`OpenSSL` to create a TLS connection. Optionally, the TLS handshake can be -deferred and performed later using :meth:`STARTTLSTransport.starttls`. - -.. note:: - - Use this module at your own risk. It has lower test coverage than I’d like - it to have; it has been exported from aioxmpp on request, where it undergoes - implicit testing. If you find bugs, please report them. If possible, add - regression tests while you’re at it. - - If you find security-critical bugs, please follow the procedure announced in - the `aioxmpp readme `_. - - -The following function can be used to create a connection using the -:class:`STARTTLSTransport`, which itself is documented below: - -.. autofunction:: create_starttls_connection - -The transport implementation is documented below: - -.. autoclass:: STARTTLSTransport(loop, rawsock, protocol, ssl_context_factory, [waiter=None], [use_starttls=False], [post_handshake_callback=None], [peer_hostname=None], [server_hostname=None]) - :members: - -""" - -import asyncio -import logging -import socket -import typing - -from enum import Enum - -from .version import __version__, version_info, version # noqa:F401 -from .utils import SendWrap - -import OpenSSL.SSL - -logger = logging.getLogger(__name__) - - -class _State(Enum): - RAW_OPEN = 0x0000 # noqa:E221 - RAW_EOF_RECEIVED = 0x0001 # noqa:E221 - - TLS_HANDSHAKING = 0x0300 # noqa:E221 - TLS_OPEN = 0x0100 # noqa:E221 - TLS_EOF_RECEIVED = 0x0101 # noqa:E221 - TLS_SHUTTING_DOWN = 0x0102 # noqa:E221 - TLS_SHUT_DOWN = 0x0103 # noqa:E221 - - CLOSED = 0x0003 # noqa:E221 - - @property - def eof_received(self) -> bool: - return bool(self.value & 0x0001) - - @property - def tls_started(self) -> bool: - return bool(self.value & 0x0100) - - @property - def tls_handshaking(self) -> bool: - return bool(self.value & 0x0200) - - @property - def is_writable(self) -> bool: - return not bool(self.value & 0x0002) - - @property - def is_open(self) -> bool: - return (self.value & 0x3) == 0 - - -SSLContextFactory = typing.Callable[[asyncio.Transport], OpenSSL.SSL.Context] -PostHandshakeCallback = typing.Callable[ - ["STARTTLSTransport"], - typing.Coroutine[typing.Any, typing.Any, None], -] - - -class STARTTLSTransport(asyncio.Transport): - """ - Create a new :class:`asyncio.Transport` which supports TLS and the deferred - starting of TLS using the :meth:`starttls` method. - - `loop` must be a :class:`asyncio.BaseEventLoop` with support for - :meth:`BaseEventLoop.add_reader` as well as removal and the writer - complements. - - `rawsock` must be a :class:`socket.socket` which will be used as the socket - for the transport. `protocol` must be a :class:`asyncio.Protocol` which - will be fed the data the transport receives. - - `ssl_context_factory` must be a callable accepting a single positional - argument which returns a :class:`OpenSSL.SSL.Context`. The transport will - be passed as the argument to the factory. The returned context will be used - to create the :class:`OpenSSL.SSL.Connection` when TLS is enabled on the - transport. If the callable is :data:`None`, a `ssl_context` must be - supplied to :meth:`starttls` and `use_starttls` must be true. - - `use_starttls` must be a boolean value. If it is true, TLS is not enabled - immediately. Instead, the user must call :meth:`starttls` to enable TLS on - the transport. Until that point, the transport is unencrypted. If it is - false, the TLS handshake is started immediately. This is roughly equivalent - to calling :meth:`starttls` immediately. - - `peer_hostname` must be either a :class:`str` or :data:`None`. It may be - used by certificate validators and must be the host name this transport - actually connected to. That might be (e.g. in the case of XMPP) different - from the actual domain name the transport communicates with (and for which - the service must have a valid certificate). This host name may be used by - certificate validators implementing e.g. DANE. - - `server_hostname` must be either a :class:`str` or :data:`None`. It may be - used by certificate validators anrd must be the host name for which the - peer must have a valid certificate (if host name based certificate - validation is performed). `server_hostname` is also passed via the TLS - Server Name Indication (SNI) extension if it is given. - - If host names are to be converted to :class:`bytes` by the transport, they - are encoded using the ``utf-8`` codec. - - If `waiter` is not :data:`None`, it must be a - :class:`asyncio.Future`. After the stream has been established, the futures - result is set to a value of :data:`None`. If any errors occur, the - exception is set on the future. - - If `use_starttls` is true, the future is fulfilled immediately after - construction, as there is no blocking process which needs to take place. If - `use_starttls` is false and thus TLS negotiation starts right away, the - future is fulfilled when TLS negotiation is complete. - - `post_handshake_callback` may be a coroutine or :data:`None`. If it is not - :data:`None`, it is called asynchronously after the TLS handshake and - blocks the completion of the TLS handshake until it returns. - - It can be used to perform blocking post-handshake certificate verification, - e.g. using DANE. The coroutine must not return a value. If it encounters an - error, an appropriate exception should be raised, which will propagate out - of :meth:`starttls` and/or passed to the `waiter` future. - """ - - MAX_SIZE = 256 * 1024 - - def __init__( - self, - loop: asyncio.BaseEventLoop, - rawsock: socket.socket, - protocol: asyncio.Protocol, - ssl_context_factory: typing.Optional[SSLContextFactory] = None, - waiter: typing.Optional[asyncio.Future] = None, - use_starttls: bool = False, - post_handshake_callback: typing.Optional[ - PostHandshakeCallback - ] = None, - peer_hostname: typing.Optional[str] = None, - server_hostname: typing.Optional[str] = None): - if not use_starttls and not ssl_context_factory: - raise ValueError("Cannot have STARTTLS disabled (i.e. immediate " - "TLS connection) and without SSL context.") - - super().__init__() - self._rawsock = rawsock - self._raw_fd = rawsock.fileno() - self._trace_logger = logger.getChild( - "trace.fd={}".format(self._raw_fd) - ) - self._sock = rawsock # type: typing.Union[socket.socket, OpenSSL.SSL.Connection] # noqa - self._send_wrap = SendWrap(self._sock) - self._protocol = protocol - self._loop = loop - self._extra = { - "socket": rawsock, - } # type: typing.Dict[str, typing.Any] - self._waiter = waiter - self._conn_lost = 0 - self._buffer = bytearray() - self._ssl_context_factory = ssl_context_factory - self._extra.update( - sslcontext=None, - ssl_object=None, - peername=self._rawsock.getpeername(), - peer_hostname=peer_hostname, - server_hostname=server_hostname - ) - - # this is a list set of tasks which will also be cancelled if the - # _waiter is cancelled - self._chained_pending = set() # type: typing.Set[asyncio.Future] - - self._paused = False - self._closing = False - - self._tls_conn = None # type: typing.Optional[OpenSSL.SSL.Connection] - self._tls_read_wants_write = False - self._tls_write_wants_read = False - self._tls_post_handshake_callback = post_handshake_callback - - self._state = None # type: typing.Optional[_State] - if not use_starttls: - assert ssl_context_factory is not None - self._ssl_context = ssl_context_factory(self) - self._extra.update( - sslcontext=self._ssl_context, - ) - self._initiate_tls() - else: - self._initiate_raw() - - def _waiter_done(self, fut: asyncio.Future) -> None: - self._trace_logger.debug("_waiter future done (%r)", fut) - - for chained in self._chained_pending: - self._trace_logger.debug("cancelling chained %r", chained) - chained.cancel() - self._chained_pending.clear() - - def _invalid_transition( - self, - via: typing.Optional[str] = None, - to: typing.Optional[_State] = None) -> None: - via_text = (" via {}".format(via)) if via is not None else "" - to_text = (" to {}".format(to)) if to is not None else "" - msg = "Invalid state transition (from {}{}{})".format( - self._state, - via_text, - to_text - ) - logger.error(msg) - raise RuntimeError(msg) - - def _invalid_state( - self, - what: str, - exc: typing.Type[Exception] = RuntimeError, - ) -> Exception: - msg = "{what} (invalid in state {state}, closing={closing})".format( - what=what, - state=self._state, - closing=self._closing) - logger.error(msg) - # raising is optional :) - return exc(msg) - - def _fatal_error( - self, - exc: BaseException, - msg: str) -> None: - if not isinstance(exc, (BrokenPipeError, ConnectionResetError)): - self._loop.call_exception_handler({ - "message": msg, - "exception": exc, - "transport": self, - "protocol": self._protocol - }) - - self._force_close(exc) - - def _force_close( - self, - exc: typing.Optional[BaseException], - ) -> None: - self._trace_logger.debug("_force_close called") - self._remove_rw() - if self._state == _State.CLOSED: - raise self._invalid_state("_force_close called") - - self._state = _State.CLOSED - - if self._buffer: - self._buffer.clear() - - if self._waiter is not None and not self._waiter.done(): - self._waiter.set_exception( - exc or ConnectionError("_force_close() called"), - ) - self._loop.remove_reader(self._raw_fd) - self._loop.remove_writer(self._raw_fd) - self._loop.call_soon(self._call_connection_lost_and_clean_up, exc) - - def _remove_rw(self) -> None: - self._trace_logger.debug("clearing readers/writers") - self._loop.remove_reader(self._raw_fd) - self._loop.remove_writer(self._raw_fd) - - def _call_connection_lost_and_clean_up( - self, - exc: Exception, - ) -> None: - """ - Clean up all resources and call the protocols connection lost method. - """ - - self._state = _State.CLOSED - try: - self._protocol.connection_lost(exc) - finally: - self._rawsock.close() - if self._tls_conn is not None: - self._tls_conn.set_app_data(None) - self._tls_conn = None - self._rawsock = None # type:ignore - self._protocol = None # type:ignore - - def _initiate_raw(self) -> None: - if self._state is not None: - self._invalid_transition(via="_initiate_raw", to=_State.RAW_OPEN) - - self._state = _State.RAW_OPEN - self._loop.add_reader(self._raw_fd, self._read_ready) - self._loop.call_soon(self._protocol.connection_made, self) - if self._waiter is not None: - self._loop.call_soon(self._waiter.set_result, None) - self._waiter = None - - def _initiate_tls(self) -> None: - self._trace_logger.debug("_initiate_tls called") - if self._state is not None and self._state != _State.RAW_OPEN: - self._invalid_transition(via="_initiate_tls", - to=_State.TLS_HANDSHAKING) - - self._tls_was_starttls = (self._state == _State.RAW_OPEN) - self._state = _State.TLS_HANDSHAKING - self._tls_conn = OpenSSL.SSL.Connection( - self._ssl_context, - self._sock) - self._tls_conn.set_connect_state() - self._tls_conn.set_app_data(self) - try: - self._tls_conn.set_tlsext_host_name( - self._extra["server_hostname"].encode("IDNA")) - except KeyError: - pass - self._sock = self._tls_conn - self._send_wrap = SendWrap(self._sock) - self._extra.update( - ssl_object=self._tls_conn - ) - - self._tls_do_handshake() - - def _tls_do_handshake(self) -> None: - assert self._tls_conn is not None - self._trace_logger.debug("_tls_do_handshake called") - if self._state != _State.TLS_HANDSHAKING: - raise self._invalid_state("_tls_do_handshake called") - - try: - self._tls_conn.do_handshake() - except OpenSSL.SSL.WantReadError: - self._trace_logger.debug( - "registering reader for _tls_do_handshake") - self._loop.add_reader(self._raw_fd, self._tls_do_handshake) - return - except OpenSSL.SSL.WantWriteError: - self._trace_logger.debug( - "registering writer for _tls_do_handshake") - self._loop.add_writer(self._raw_fd, self._tls_do_handshake) - return - except Exception as exc: - self._remove_rw() - self._fatal_error(exc, "Fatal error on tls handshake") - if self._waiter is not None: - self._waiter.set_exception(exc) - return - except BaseException as exc: - self._remove_rw() - if self._waiter is not None: - self._waiter.set_exception(exc) - raise - - self._remove_rw() - - # handshake complete - - self._trace_logger.debug("handshake complete") - self._extra.update( - peercert=self._tls_conn.get_peer_certificate() - ) - - if self._tls_post_handshake_callback: - self._trace_logger.debug("post handshake scheduled via callback") - task = asyncio.ensure_future( - self._tls_post_handshake_callback(self) - ) - task.add_done_callback(self._tls_post_handshake_done) - self._chained_pending.add(task) - self._tls_post_handshake_callback = None - else: - self._tls_post_handshake(None) - - def _tls_post_handshake_done( - self, - task: asyncio.Future, - ) -> None: - self._chained_pending.discard(task) - try: - task.result() - except asyncio.CancelledError: - # canceled due to closure or something similar - pass - except BaseException as err: - self._tls_post_handshake(err) - else: - self._tls_post_handshake(None) - - def _tls_post_handshake( - self, - exc: typing.Optional[BaseException], - ) -> None: - self._trace_logger.debug("_tls_post_handshake called") - if exc is not None: - if self._waiter is not None and not self._waiter.done(): - self._waiter.set_exception(exc) - self._fatal_error(exc, "Fatal error on post-handshake callback") - return - - self._tls_read_wants_write = False - self._tls_write_wants_read = False - - self._state = _State.TLS_OPEN - - self._loop.add_reader(self._raw_fd, self._read_ready) - if not self._tls_was_starttls: - self._loop.call_soon(self._protocol.connection_made, self) - if self._waiter is not None: - self._loop.call_soon(self._waiter.set_result, None) - - def _tls_do_shutdown(self) -> None: - self._trace_logger.debug("_tls_do_shutdown called") - if self._state != _State.TLS_SHUTTING_DOWN: - raise self._invalid_state("_tls_do_shutdown called") - - assert isinstance(self._sock, OpenSSL.SSL.Connection) - try: - self._sock.shutdown() - except OpenSSL.SSL.WantReadError: - self._trace_logger.debug("registering reader for _tls_shutdown") - self._loop.add_reader(self._raw_fd, self._tls_shutdown) - return - except OpenSSL.SSL.WantWriteError: - self._trace_logger.debug("registering writer for _tls_shutdown") - self._loop.add_writer(self._raw_fd, self._tls_shutdown) - return - except Exception as exc: - # force_close will take care of removing rw handlers - self._fatal_error(exc, "Fatal error on tls shutdown") - return - except BaseException: - self._remove_rw() - raise - - self._remove_rw() - self._state = _State.TLS_SHUT_DOWN - # continue to raw shut down - self._raw_shutdown() - - def _tls_shutdown(self) -> None: - self._state = _State.TLS_SHUTTING_DOWN - self._tls_do_shutdown() - - def _raw_shutdown(self) -> None: - self._remove_rw() - try: - self._rawsock.shutdown(socket.SHUT_RDWR) - except OSError: - # we cannot do anything anyway if this fails - pass - self._force_close(None) - - def _read_ready(self) -> None: - assert self._state is not None - if self._state.tls_started and self._tls_write_wants_read: - self._tls_write_wants_read = False - self._write_ready() - - if self._buffer: - self._trace_logger.debug("_read_ready: add writer for more" - " data") - self._loop.add_writer(self._raw_fd, self._write_ready) - - if self._state.eof_received: - # no further reading - return - - try: - data = self._sock.recv(self.MAX_SIZE) - except (BlockingIOError, InterruptedError, OpenSSL.SSL.WantReadError): - pass - except OpenSSL.SSL.WantWriteError: - assert self._state.tls_started - self._tls_read_wants_write = True - self._trace_logger.debug("_read_ready: swap reader for writer") - self._loop.remove_reader(self._raw_fd) - self._loop.add_writer(self._raw_fd, self._write_ready) - except OpenSSL.SSL.SysCallError as exc: - if self._state in (_State.TLS_SHUT_DOWN, - _State.TLS_SHUTTING_DOWN, - _State.CLOSED): - self._trace_logger.debug( - "_read_ready: ignoring syscall exception during shutdown: " - "%s", - exc, - ) - else: - self._fatal_error(exc, - "Fatal read error on STARTTLS transport") - except Exception as err: - self._fatal_error(err, "Fatal read error on STARTTLS transport") - return - else: - if data: - self._protocol.data_received(data) - else: - keep_open = False - try: - keep_open = bool(self._protocol.eof_received()) - finally: - self._eof_received(keep_open) - - def _write_ready(self) -> None: - assert self._state is not None - if self._tls_read_wants_write: - self._tls_read_wants_write = False - self._read_ready() - - if not self._paused and not self._state.eof_received: - self._trace_logger.debug("_write_ready: add reader for more" - " data") - self._loop.add_reader(self._raw_fd, self._read_ready) - - # do not send data during handshake! - if self._buffer and self._state != _State.TLS_HANDSHAKING: - try: - nsent = self._send_wrap.send(self._buffer) - except (BlockingIOError, InterruptedError, - OpenSSL.SSL.WantWriteError): - nsent = 0 - except OpenSSL.SSL.WantReadError: - nsent = 0 - assert self._state.tls_started - self._tls_write_wants_read = True - self._trace_logger.debug( - "_write_ready: swap writer for reader") - self._loop.remove_writer(self._raw_fd) - self._loop.add_reader(self._raw_fd, self._read_ready) - except OpenSSL.SSL.SysCallError as exc: - if self._state in (_State.TLS_SHUT_DOWN, - _State.TLS_SHUTTING_DOWN, - _State.CLOSED): - self._trace_logger.debug( - "_write_ready: ignoring syscall exception during " - "shutdown: %s", - exc, - ) - else: - self._fatal_error(exc, - "Fatal write error on STARTTLS " - "transport") - except Exception as err: - self._fatal_error(err, - "Fatal write error on STARTTLS " - "transport") - return - - if nsent: - del self._buffer[:nsent] - - if not self._buffer: - if not self._tls_read_wants_write: - self._trace_logger.debug("_write_ready: nothing more to write," - " removing writer") - self._loop.remove_writer(self._raw_fd) - if self._closing: - if self._state.tls_started: - self._tls_shutdown() - else: - self._raw_shutdown() - - def _eof_received(self, keep_open: bool) -> None: - assert self._state is not None - self._trace_logger.debug("_eof_received: removing reader") - self._loop.remove_reader(self._raw_fd) - if self._state.tls_started: - assert self._tls_conn is not None - if self._tls_conn.get_shutdown() & OpenSSL.SSL.RECEIVED_SHUTDOWN: - # proper TLS shutdown going on - if keep_open: - self._state = _State.TLS_EOF_RECEIVED - else: - self._tls_shutdown() - else: - if keep_open: - self._trace_logger.warning( - "result of eof_received() ignored as shut down is" - " improper", - ) - self._fatal_error( - ConnectionError("Underlying transport closed"), - "unexpected eof_received" - ) - else: - if keep_open: - self._state = _State.RAW_EOF_RECEIVED - else: - self._raw_shutdown() - - # public API - - def abort(self) -> None: - """ - Immediately close the stream, without sending remaining buffers or - performing a proper shutdown. - """ - if self._state == _State.CLOSED: - self._invalid_state("abort() called") - return - - self._force_close(None) - - def can_write_eof(self) -> bool: - """ - Return :data:`False`. - - .. note:: - - Writing of EOF (i.e. closing the sending direction of the stream) is - theoretically possible. However, it was deemed by the author that - the case is rare enough to neglect it for the sake of implementation - simplicity. - - """ - return False - - def close(self) -> None: - """ - Close the stream. This performs a proper stream shutdown, except if the - stream is currently performing a TLS handshake. In that case, calling - :meth:`close` is equivalent to calling :meth:`abort`. - - Otherwise, the transport waits until all buffers are transmitted. - """ - - if self._state == _State.CLOSED: - self._invalid_state("close() called") - return - - if self._state == _State.TLS_HANDSHAKING: - # hard-close - self._force_close(None) - elif self._state == _State.TLS_SHUTTING_DOWN: - # shut down in progress, nothing to do - pass - elif self._buffer: - # there is data to be send left, first wait for it to transmit ... - self._closing = True - elif self._state is not None and self._state.tls_started: - # normal TLS state, nothing left to transmit, shut down - self._tls_shutdown() - else: - # normal non-TLS state, nothing left to transmit, close - self._raw_shutdown() - - def get_extra_info( - self, - name: str, - default: typing.Optional[typing.Any] = None, - ) -> typing.Any: - """ - The following extra information is available: - - * ``socket``: the underlying :mod:`socket` object - * ``sslcontext``: the :class:`OpenSSL.SSL.Context` object to use (this - may be :data:`None` until :meth:`starttls` has been called) - * ``ssl_object``: :class:`OpenSSL.SSL.Connection` object (:data:`None` - if TLS is not enabled (yet)) - * ``peername``: return value of :meth:`socket.Socket.getpeername` - * ``peer_hostname``: The `peer_hostname` value passed to the - constructor. - * ``server_hostname``: The `server_hostname` value passed to the - constructor. - - """ - return self._extra.get(name, default) - - async def starttls( - self, - ssl_context: typing.Optional[OpenSSL.SSL.Context] = None, - post_handshake_callback: typing.Optional[ - PostHandshakeCallback - ] = None, - ) -> None: - """ - Start a TLS stream on top of the socket. This is an invalid operation - if the stream is not in RAW_OPEN state. - - If `ssl_context` is set, it overrides the `ssl_context` passed to the - constructor. If `post_handshake_callback` is set, it overrides the - `post_handshake_callback` passed to the constructor. - - .. versionchanged:: 0.4 - - This method is now a barrier with respect to reads and writes: - before the handshake is completed (including the post handshake - callback, if any), no data is received or sent. - """ - if self._state != _State.RAW_OPEN or self._closing: - raise self._invalid_state("starttls() called") - - if ssl_context is not None: - self._ssl_context = ssl_context - self._extra.update( - sslcontext=ssl_context - ) - else: - assert self._ssl_context_factory is not None - self._ssl_context = self._ssl_context_factory(self) - - if post_handshake_callback is not None: - self._tls_post_handshake_callback = post_handshake_callback - - self._waiter = asyncio.Future() - self._waiter.add_done_callback(self._waiter_done) - self._initiate_tls() - try: - await self._waiter - finally: - self._waiter = None - - def write(self, data: typing.Union[bytes, bytearray, memoryview]) -> None: - """ - Write data to the transport. This is an invalid operation if the stream - is not writable, that is, if it is closed. During TLS negotiation, the - data is buffered. - """ - if not isinstance(data, (bytes, bytearray, memoryview)): - raise TypeError('data argument must be byte-ish (%r)', - type(data)) - - if (self._state is None or - not self._state.is_writable or - self._closing): - raise self._invalid_state("write() called") - - if not data: - return - - if not self._buffer: - self._loop.add_writer(self._raw_fd, self._write_ready) - - self._buffer.extend(data) - - def write_eof(self) -> None: - """ - Writing the EOF has not been implemented, for the sake of simplicity. - """ - raise NotImplementedError("Cannot write_eof() on STARTTLS transport") - - def can_starttls(self) -> bool: - """ - Return :data:`True`. - """ - return True - - def is_closing(self) -> bool: - return (self._state == _State.TLS_SHUTTING_DOWN or - self._state == _State.CLOSED) - - -async def create_starttls_connection( - loop: asyncio.BaseEventLoop, - protocol_factory: typing.Callable[[], asyncio.Protocol], - host: typing.Optional[str] = None, - port: typing.Optional[int] = None, - *, - sock: typing.Optional[socket.socket] = None, - ssl_context_factory: typing.Optional[SSLContextFactory] = None, - use_starttls: bool = False, - local_addr: typing.Any = None, - **kwargs # type: typing.Any - ) -> typing.Tuple[asyncio.Transport, asyncio.Protocol]: - """ - Create a connection which can later be upgraded to use TLS. - - .. versionchanged:: 0.4 - - The `local_addr` argument was added. - - :param loop: The event loop to use. - :type loop: :class:`asyncio.BaseEventLoop` - :param protocol_factory: Factory for the protocol for the connection - :param host: The host name or address to connect to - :type host: :class:`str` or :data:`None` - :param port: The port to connect to - :type port: :class:`int` or :data:`None` - :param sock: A socket to wrap (conflicts with `host` and `port`) - :type sock: :class:`socket.socket` - :param ssl_context_factory: Function which returns a - :class:`OpenSSL.SSL.Context` to use for TLS operations - :param use_starttls: Flag to control whether TLS is negotiated right away - or deferredly. - :type use_starttls: :class:`bool` - :param local_addr: Address to bind to - - This is roughly a copy of the asyncio implementation of - :meth:`asyncio.BaseEventLoop.create_connection`. It returns a pair - ``(transport, protocol)``, where `transport` is a newly created - :class:`STARTTLSTransport` instance. Further keyword arguments are - forwarded to the constructor of :class:`STARTTLSTransport`. - - `loop` must be a :class:`asyncio.BaseEventLoop`, with support for - :meth:`asyncio.BaseEventLoop.add_reader` and the corresponding writer and - removal functions for sockets. This is typically a selector type event - loop. - - `protocol_factory` must be a callable which (without any arguments) returns - a :class:`asyncio.Protocol` which will be connected to the STARTTLS - transport. - - `host` and `port` must be a hostname and a port number, or both - :data:`None`. Both must be :data:`None`, if and only if `sock` is not - :data:`None`. In that case, `sock` is used instead of a newly created - socket. `sock` is put into non-blocking mode and must be a stream socket. - - If `use_starttls` is :data:`True`, no TLS handshake will be performed - initially. Instead, the connection is established without any - transport-layer security. It is expected that the - :meth:`STARTTLSTransport.starttls` method is used when the application - protocol requires TLS. If `use_starttls` is :data:`False`, the TLS - handshake is initiated right away. - - `local_addr` may be an address to bind this side of the socket to. If - omitted or :data:`None`, the local address is assigned by the operating - system. - - This coroutine returns when the stream is established. If `use_starttls` is - :data:`False`, this means that the full TLS handshake has to be finished - for this coroutine to return. Otherwise, no TLS handshake takes place. It - must be invoked using the :meth:`STARTTLSTransport.starttls` coroutine. - """ - - if host is not None and port is not None: - host_addrs = await loop.getaddrinfo( - host, port, - type=socket.SOCK_STREAM, - ) - - exceptions = [] - - for family, type, proto, cname, address in host_addrs: - sock = None - try: - sock = socket.socket(family=family, type=type, proto=proto) - sock.setblocking(False) - if local_addr is not None: - sock.bind(local_addr) - await loop.sock_connect(sock, address) - except OSError as exc: - if sock is not None: - sock.close() - exceptions.append(exc) - else: - break - else: - if len(exceptions) == 1: - raise exceptions[0] - - model = str(exceptions[0]) - if all(str(exc) == model for exc in exceptions): - raise exceptions[0] - - try: - from aioxmpp.errors import MultiOSError # type:ignore - except ImportError: - MultiOSError = OSError - - raise MultiOSError( - "could not connect to [{}]:{}".format(host, port), - exceptions, - ) - elif sock is None: - raise ValueError("sock must not be None if host and/or port are None") - else: - sock.setblocking(False) - - protocol = protocol_factory() - waiter = asyncio.Future(loop=loop) # type: asyncio.Future[None] - transport = STARTTLSTransport(loop, sock, protocol, - ssl_context_factory=ssl_context_factory, - waiter=waiter, - use_starttls=use_starttls, - **kwargs) - await waiter - - return transport, protocol diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index ab19297..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/utils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/utils.cpython-311.pyc deleted file mode 100644 index 2eac9e8..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/utils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/version.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/version.cpython-311.pyc deleted file mode 100644 index 9e1f50a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioopenssl/__pycache__/version.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl/utils.py b/tests/venv2/lib/python3.11/site-packages/aioopenssl/utils.py deleted file mode 100644 index 716bf46..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -import typing - -import OpenSSL.SSL - - -class SendWrap: - def __init__(self, sock: OpenSSL.SSL.Connection): - self.__sock = sock - self.__cached_write = None # type: typing.Optional[typing.Tuple[bytes, typing.Any]] # noqa - - def send(self, buf: typing.Union[bytes, memoryview]) -> int: - if self.__cached_write is not None: - as_bytes, prev_buf = self.__cached_write - if prev_buf is not buf: - raise ValueError( - "this looks like a mistake: the previous send received a " - "different buffer object" - ) - self.__cached_write = None - else: - as_bytes = bytes(buf) - - try: - return self.__sock.send(as_bytes) - except (OpenSSL.SSL.WantWriteError, OpenSSL.SSL.WantReadError): - self.__cached_write = as_bytes, buf - raise diff --git a/tests/venv2/lib/python3.11/site-packages/aioopenssl/version.py b/tests/venv2/lib/python3.11/site-packages/aioopenssl/version.py deleted file mode 100644 index 8b63fa4..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioopenssl/version.py +++ /dev/null @@ -1,7 +0,0 @@ -version_info = (0, 6, 0, None) - -__version__ = ".".join(map(str, version_info[:3])) + ( - "-"+version_info[3] if version_info[3] is not None else "" # type:ignore -) - -version = __version__ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/PKG-INFO b/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/PKG-INFO deleted file mode 100644 index 2547ca6..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/PKG-INFO +++ /dev/null @@ -1,59 +0,0 @@ -Metadata-Version: 2.1 -Name: aiosasl -Version: 0.5.0 -Summary: Pure-python, protocol agnostic SASL library for asyncio -Home-page: https://github.com/horazont/aiosasl -Author: Jonas Wielicki -Author-email: jonas@wielicki.name -License: LGPLv3+ -Keywords: asyncio sasl library -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) -License-File: LICENSES -License-File: COPYING.LESSER -License-File: COPYING.gpl3 - -``aiosasl``, pure python generic asyncio SASL library -##################################################### - -.. image:: https://github.com/horazont/aiosasl/workflows/CI/badge.svg - :target: https://github.com/horazont/aiosasl/actions?query=workflow%3ACI+branch%3Adevel - -.. image:: https://coveralls.io/repos/github/horazont/aiosasl/badge.svg?branch=devel - :target: https://coveralls.io/github/horazont/aiosasl?branch=devel - -``aiosasl`` provides a generic, asyncio-based SASL library. It can be used with -any protocol, provided the neccessary interface code is provided by the -application or protocol implementation. - -Dependencies ------------- - -* Python ≥ 3.5 - -Supported SASL mechanisms -------------------------- - -* ``PLAIN``: authenticate with plaintext password (RFC 4616) -* ``ANONYMOUS``: anonymous "authentication" (RFC 4505) -* ``SCRAM-SHA-1`` and ``SCRAM-SHA-256`` (and the ``-PLUS`` variants with - channel binding): Salted Challenge Response Authentication (RFC 5802) - -Documentation -------------- - -Official documentation can be built with sphinx and is available online -`on our servers `_. - -Supported channel binding methods ---------------------------------- - -* ``tls-unique`` and ``tls-server-end-point`` with a pyOpenSSL connection -* all methods supported by the Python standard library when using the - ``ssl`` module diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/SOURCES.txt b/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/SOURCES.txt deleted file mode 100644 index 78514b4..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/SOURCES.txt +++ /dev/null @@ -1,21 +0,0 @@ -COPYING.LESSER -COPYING.gpl3 -LICENSES -MANIFEST.in -README.rst -setup.cfg -setup.py -aiosasl/__init__.py -aiosasl/anonymous.py -aiosasl/channel_binding.py -aiosasl/common.py -aiosasl/plain.py -aiosasl/scram.py -aiosasl/statemachine.py -aiosasl/stringprep.py -aiosasl/utils.py -aiosasl/version.py -aiosasl.egg-info/PKG-INFO -aiosasl.egg-info/SOURCES.txt -aiosasl.egg-info/dependency_links.txt -aiosasl.egg-info/top_level.txt \ No newline at end of file diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/dependency_links.txt b/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/installed-files.txt b/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/installed-files.txt deleted file mode 100644 index 964f02b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/installed-files.txt +++ /dev/null @@ -1,24 +0,0 @@ -../aiosasl/__init__.py -../aiosasl/__pycache__/__init__.cpython-311.pyc -../aiosasl/__pycache__/anonymous.cpython-311.pyc -../aiosasl/__pycache__/channel_binding.cpython-311.pyc -../aiosasl/__pycache__/common.cpython-311.pyc -../aiosasl/__pycache__/plain.cpython-311.pyc -../aiosasl/__pycache__/scram.cpython-311.pyc -../aiosasl/__pycache__/statemachine.cpython-311.pyc -../aiosasl/__pycache__/stringprep.cpython-311.pyc -../aiosasl/__pycache__/utils.cpython-311.pyc -../aiosasl/__pycache__/version.cpython-311.pyc -../aiosasl/anonymous.py -../aiosasl/channel_binding.py -../aiosasl/common.py -../aiosasl/plain.py -../aiosasl/scram.py -../aiosasl/statemachine.py -../aiosasl/stringprep.py -../aiosasl/utils.py -../aiosasl/version.py -PKG-INFO -SOURCES.txt -dependency_links.txt -top_level.txt diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/top_level.txt b/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/top_level.txt deleted file mode 100644 index ba880df..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl-0.5.0.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -aiosasl diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__init__.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/__init__.py deleted file mode 100644 index 6d86a4b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/__init__.py +++ /dev/null @@ -1,166 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -Using SASL in a protocol -======================== - -To make use of SASL over an existing protocol, you first need to subclass and -implement :class:`SASLInterface`. - -The usable mechanisms need to be detected by your application using the -protocol over which to implement SASL. This is generally protocol-specific. For -example, XMPP uses stream features to announce which SASL mechanisms are -supported by the server. - -When a set of SASL mechanism strings has been obtained by the server (let us -call a set with the mechanism strings ``sasl_mechanisms``), the mechanisms -supported by your application (a list of :class:`SASLMechanism` subclass -instances, let us call it ``mechanism_impls``) can be queried for support:: - - # intf = - for impl in mechanism_impl: - token = impl.any_supported(sasl_mechanisms) - if token is not None: - sm = aiosasl.SASLStateMachine(intf) - try: - await impl.authenticate(sm, token) - except aiosasl.AuthenticationFailure: - # handle authentication failure - # it is generally not sensible to re-try with other mechanisms - except aiosasl.SASLFailure: - # this is a protocol problem, it is sensible to re-try other - # mechanisms - else: - # authentication was successful! - -The instances for the mechanisms can be re-used; they do not save any state, -the state is held by :class:`SASLStateMachine` instead. The different -mechanisms require different arguments (the password-based mechanisms generally -require a callback which provides credentials). - -The mechanisms which are currently supported by :mod:`aiosasl` are summarised -below: - -.. autosummary:: - - ANONYMOUS - PLAIN - SCRAM - SCRAMPLUS - -Interface for protocols using SASL -================================== - -To implement SASL on an existing protocol, you need to subclass -:class:`SASLInterface` and implement the abstract methods: - -.. autoclass:: SASLInterface - -.. autoclass:: SASLState - -SASL mechansims -=============== - -.. autoclass:: PLAIN - -.. autoclass:: SCRAM(credential_provider, *[, after_scram_plus=False][, enforce_minimum_iteration_count=True]) - -.. autoclass:: SCRAMPLUS(credential_provider, cb_provider, *[, enforce_minimum_iteration_count=True]) - -.. autoclass:: ANONYMOUS - -Base class ----------- - -.. autoclass:: SASLMechanism - -A note for implementers ------------------------ - -The :class:`SASLStateMachine` unwraps `(SASLState.SUCCESS, payload)` messages -passed in from a :class:`SASLInterface` to the equivalent sequence -`(SASLState.CHALLENGE, payload)` (requiring the empty string as response) and -`(SASLState.SUCCESS, None)`. The two forms are equivalent as per the SASL -specification and this unwrapping allows uniform treatment of both -forms by the :class:`SASLMechanism` implementations. - -SASL state machine -================== - -.. autoclass:: SASLStateMachine - -Exception classes -================= - -.. autoclass:: SASLError - -.. autoclass:: SASLFailure - -.. autoclass:: AuthenticationFailure - -Version information -=================== - -.. autodata:: __version__ - -.. autodata:: version_info -""" # NOQA - -from .common import ( # noqa:F401 - AuthenticationFailure, - SASLError, - SASLFailure, - SASLState, -) - -from .statemachine import ( # noqa:F401 - SASLInterface, - SASLMechanism, - SASLStateMachine, -) - -from .scram import ( # noqa:F401 - SCRAM, - SCRAMPLUS, -) - -from .plain import ( # noqa:F401 - PLAIN, -) - -from .anonymous import ( # noqa:F401 - ANONYMOUS, -) - -from .version import version, __version__, version_info # noqa:F401 - -#: The imported :mod:`aiosasl` version as a tuple. -#: -#: The components of the tuple are, in order: `major version`, `minor version`, -#: `patch level`, and `pre-release identifier`. -version_info = version_info - -#: The imported :mod:`aiosasl` version as a string. -#: -#: The version number is dot-separated; in pre-release or development versions, -#: the version number is followed by a hypen-separated pre-release identifier. -__version__ = __version__ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index d9ac09f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/anonymous.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/anonymous.cpython-311.pyc deleted file mode 100644 index af8085e..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/anonymous.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/channel_binding.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/channel_binding.cpython-311.pyc deleted file mode 100644 index 0f7f0a1..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/channel_binding.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/common.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/common.cpython-311.pyc deleted file mode 100644 index d2b2a4c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/common.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/plain.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/plain.cpython-311.pyc deleted file mode 100644 index 8419e15..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/plain.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/scram.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/scram.cpython-311.pyc deleted file mode 100644 index 9d2700c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/scram.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/statemachine.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/statemachine.cpython-311.pyc deleted file mode 100644 index 7320856..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/statemachine.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/stringprep.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/stringprep.cpython-311.pyc deleted file mode 100644 index fe85d4e..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/stringprep.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/utils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/utils.cpython-311.pyc deleted file mode 100644 index 64cee9b..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/utils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/version.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/version.cpython-311.pyc deleted file mode 100644 index 9fa5850..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aiosasl/__pycache__/version.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/anonymous.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/anonymous.py deleted file mode 100644 index 79b93f0..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/anonymous.py +++ /dev/null @@ -1,65 +0,0 @@ -######################################################################## -# File name: anonymous.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import logging -import typing - -from . import common, statemachine, stringprep - - -logger = logging.getLogger() - - -class ANONYMOUS(statemachine.SASLMechanism): - """ - The ANONYMOUS SASL mechanism (see :rfc:`4505`). - - .. versionadded:: 0.3 - """ - - def __init__(self, token: str) -> None: - super().__init__() - self._token = stringprep.trace(token).encode("utf-8") - - @classmethod - def any_supported( - self, - mechanisms: typing.Iterable[str], - ) -> typing.Optional[str]: - if "ANONYMOUS" in mechanisms: - return "ANONYMOUS" - return None - - async def authenticate( - self, - sm: statemachine.SASLStateMachine, - mechanism: typing.Any) -> None: - logger.info("attempting ANONYMOUS mechanism") - - state, _ = await sm.initiate( - mechanism="ANONYMOUS", - payload=self._token - ) - - if state != common.SASLState.SUCCESS: - raise common.SASLFailure( - None, - text="SASL protocol violation") diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/channel_binding.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/channel_binding.py deleted file mode 100644 index 9b9f960..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/channel_binding.py +++ /dev/null @@ -1,159 +0,0 @@ -######################################################################## -# File name: channel_binding.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -Channel binding methods -======================= - -The module :mod:`aiosasl.channel_binding` provides -implementations of the :class:`~.ChannelBindingProvider` -interface for use with :mod:`ssl` respective :mod:`OpenSSL`. - -.. autoclass:: ChannelBindingProvider - -.. autoclass:: StdlibTLS - -.. autoclass:: TLSUnique - -.. autoclass:: TLSServerEndPoint -""" -import abc -import functools -import ssl - -try: - import OpenSSL # for mypy -except ImportError: - pass - - -class ChannelBindingProvider(metaclass=abc.ABCMeta): - """ - Interface for a channel binding method. - - The needed external information is supplied to the constructors of - the specific instances. - """ - - @abc.abstractproperty - def cb_name(self) -> bytes: - """ - Return the name of the channel-binding mechanism. - :rtype: :class:`bytes` - """ - raise NotImplementedError - - @abc.abstractmethod - def extract_cb_data(self) -> bytes: - """ - Return the channel binding data. - :returns: the channel binding data - :rtype: :class:`bytes` - """ - raise NotImplementedError - - -class StdlibTLS(ChannelBindingProvider): - """ - Provider for channel binding for :mod:`ssl`. - - :param connection: the SSL connection - :type connection: :class:`ssl.SSLSocket` - :param type_: the channel binding type - :type type_: :class:`str` - """ - - def __init__( - self, - connection: ssl.SSLSocket, - type_: str): - super().__init__() - self._connection = connection - self._type = type_ - - @property - def cb_name(self) -> bytes: - return self._type.encode("us-ascii") - - def extract_cb_data(self) -> bytes: - return self._connection.get_channel_binding(self._type) # type:ignore - - -class TLSUnique(ChannelBindingProvider): - """ - Provider for the channel binding ``tls-unique`` as specified by - :rfc:`5929` for :mod:`OpenSSL`. - - .. warning:: - - This only supports connections that were not created by session - resumption. - - :param connection: the SSL connection - :type connection: :class:`OpenSSL.SSL.Connection` - """ - - def __init__(self, connection: "OpenSSL.SSL.Connection"): - super().__init__() - self._connection = connection - - @property - def cb_name(self) -> bytes: - return b"tls-unique" - - def extract_cb_data(self) -> bytes: - return self._connection.get_finished() - - -def parse_openssl_digest( - digest: bytes, - ) -> bytes: - return bytes(map(functools.partial(int, base=16), digest.split(b":"))) - - -class TLSServerEndPoint(ChannelBindingProvider): - """ - Provider for the channel binding ``tls-server-end-point`` as - specified by :rfc:`5929` for :mod:`OpenSSL`. - - :param connection: the SSL connection - :type connection: :class:`OpenSSL.SSL.Connection` - """ - - def __init__( - self, - connection: "OpenSSL.SSL.Connection"): - super().__init__() - self._connection = connection - - @property - def cb_name(self) -> bytes: - return b"tls-server-end-point" - - def extract_cb_data(self) -> bytes: - cert = self._connection.get_peer_certificate() - algo, part, _ = cert.get_signature_algorithm().lower().partition( - b"with") - if not part: - raise NotImplementedError - if algo in (b"sha1", b"md5"): - algo = b"sha256" - return parse_openssl_digest(cert.digest(algo.decode("us-ascii"))) diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/common.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/common.py deleted file mode 100644 index 6bc7cb0..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/common.py +++ /dev/null @@ -1,170 +0,0 @@ -######################################################################## -# File name: common.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import enum -import typing - - -class SASLError(Exception): - """ - Base class for a SASL related error. `opaque_error` may be anything but - :data:`None` which helps your application re-identify the error at the - outer layers. `kind` is a string which helps identifying the class of the - error; this is set implicitly by the constructors of :class:`SASLFailure` - and :class:`AuthenticationFailure`, which you are encouraged to use. - - `text` may be a human-readable string describing the error condition in - more detail. - - `opaque_error` is set to :data:`None` by :class:`SASLMechanism` - implementations to indicate errors which originate from the local mechanism - implementation. - - .. attribute:: opaque_error - - The value passed to the respective constructor argument. - - .. attribute:: text - - The value passed to the respective constructor argument. - - """ - - def __init__( - self, - opaque_error: typing.Any, - kind: str, - text: typing.Optional[str] = None): - msg = "{}: {}".format(opaque_error, kind) - if text: - msg += ": {}".format(text) - super().__init__(msg) - self.opaque_error = opaque_error - self.text = text - - -class AuthenticationFailure(SASLError): - """ - A SASL error which indicates that the provided credentials are - invalid. This may be raised by :class:`SASLInterface` methods. - """ - - def __init__( - self, - opaque_error: typing.Any, - text: typing.Optional[str] = None): - super().__init__(opaque_error, "authentication failed", text=text) - - -class SASLFailure(SASLError): - """ - A SASL protocol failure which is unrelated to the credentials passed. This - may be raised by :class:`SASLInterface` methods. - """ - - def __init__( - self, - opaque_error: typing.Any, - text: typing.Optional[str] = None): - super().__init__(opaque_error, "SASL failure", text=text) - - def promote_to_authentication_failure(self) -> AuthenticationFailure: - return AuthenticationFailure( - self.opaque_error, - self.text) - - -class SASLState(enum.Enum): - """ - The states of the SASL state machine. - - .. attribute:: CHALLENGE - - the server sent a SASL challenge - - .. attribute:: SUCCESS - - the authentication was successful - - .. attribute:: FAILURE - - the authentication failed - - Internal states used by the state machine: - - .. attribute:: INITIAL - - the state of the state machine before the - authentication is started - - .. attribute:: SUCCESS_SIMULATE_CHALLENGE - - used to unwrap success replies that carry final data - - These internal states *must not* be returned by the - :class:`SASLInterface` methods as first component of the result - tuple. - - The following method is used to process replies returned - by the :class:`SASLInterface` methods: - - .. method:: from_reply - """ - - INITIAL = "initial" - CHALLENGE = "challenge" - SUCCESS = "success" - FAILURE = "failure" - SUCCESS_SIMULATE_CHALLENGE = "success-simulate-challenge" - - @classmethod - def from_reply(cls, state: "SASLState") -> "SASLState": - """ - Comptaibility layer for old :class:`SASLInterface` - implementations. - - Accepts the follwing set of :class:`SASLState` or strings and - maps the strings to :class:`SASLState` elements as follows: - - ``"challenge"`` - :member:`SASLState.CHALLENGE` - - ``"failue"`` - :member:`SASLState.FAILURE` - - ``"success"`` - :member:`SASLState.SUCCESS` - """ - if state in (SASLState.FAILURE, SASLState.SUCCESS, - SASLState.CHALLENGE): - return state - - if state in ("failure", "success", "challenge"): - return SASLState(state) - else: - raise RuntimeError("invalid SASL state", state) - - -NextStateTuple = typing.Tuple[SASLState, typing.Optional[bytes]] - -CredentialProvider = typing.Callable[ - [], typing.Coroutine[typing.Any, typing.Any, typing.Tuple[str, str]] -] diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/plain.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/plain.py deleted file mode 100644 index fe47631..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/plain.py +++ /dev/null @@ -1,79 +0,0 @@ -######################################################################## -# File name: plain.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import logging -import typing - -from . import common, statemachine - - -logger = logging.getLogger(__name__) - - -class PLAIN(statemachine.SASLMechanism): - """ - The password-based ``PLAIN`` SASL mechanism (see :rfc:`4616`). - - .. warning:: - - This is generally unsafe over unencrypted connections and should not be - used there. Exclusion of the ``PLAIN`` mechanism over unsafe connections - is out of scope for :mod:`aiosasl` and needs to be handled by the - protocol implementation! - - `credential_provider` must be coroutine which returns a ``(user, - password)`` tuple. - """ - def __init__(self, credential_provider: common.CredentialProvider): - super().__init__() - self._credential_provider = credential_provider - - @classmethod - def any_supported( - cls, - mechanisms: typing.Iterable[str], - ) -> typing.Any: - if "PLAIN" in mechanisms: - return "PLAIN" - return None - - async def authenticate( - self, - sm: statemachine.SASLStateMachine, - mechanism: typing.Any, - ) -> None: - logger.info("attempting PLAIN mechanism") - username, password = await self._credential_provider() - encoded_username = username.encode("utf8") - encoded_password = password.encode("utf8") - - if b"\0" in encoded_username or b"\0" in encoded_password: - raise ValueError("NUL byte in username or password is disallowed") - - state, _ = await sm.initiate( - mechanism="PLAIN", - payload=b"\0" + encoded_username + b"\0" + encoded_password, - ) - - if state != common.SASLState.SUCCESS: - raise common.SASLFailure( - None, - text="SASL protocol violation") diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/scram.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/scram.py deleted file mode 100644 index e3b5d03..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/scram.py +++ /dev/null @@ -1,394 +0,0 @@ -######################################################################## -# File name: scram.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import abc -import base64 -import collections -import functools -import hashlib -import hmac -import logging -import random -import time -import typing - -from hashlib import pbkdf2_hmac as pbkdf2 - -from . import channel_binding, common, statemachine, stringprep, utils - - -logger = logging.getLogger(__name__) - - -SCRAMHashInfo = collections.namedtuple( - "SCRAMHashInfo", - [ - "hashfun_name", - "quality", - "minimum_iteration_count", - ] -) - - -_system_random = random.SystemRandom() - - -class Base: - """ - Shared implementation of SCRAM and SCRAMPLUS. - """ - - _channel_binding = False - - def __init__( - self, - credential_provider: common.CredentialProvider, - *, - nonce_length: int = 15, - enforce_minimum_iteration_count: bool = True): - super().__init__() - self._credential_provider = credential_provider - self.nonce_length = nonce_length - self.enforce_minimum_iteration_count = enforce_minimum_iteration_count - - _supported_hashalgos = { - # the second argument is for preference ordering (highest first) - # if anyone has a better hash ordering suggestion, I’m open for it - # a value of 1 is added if the -PLUS variant is used - # -- JSC - # the minimum iteration count is obtained from - # - "SHA-1": SCRAMHashInfo("sha1", 1, 4096), - "SHA-256": SCRAMHashInfo("sha256", 256, 4096), - } - - @classmethod - def any_supported( - cls, - mechanisms: typing.Iterable[str], - ) -> typing.Optional[typing.Tuple[str, SCRAMHashInfo]]: - supported = [] - for mechanism in mechanisms: - if not mechanism.startswith("SCRAM-"): - continue - - hashfun_key = mechanism[6:] - - if cls._channel_binding: - if not mechanism.endswith("-PLUS"): - continue - hashfun_key = hashfun_key[:-5] - else: - if mechanism.endswith("-PLUS"): - continue - - try: - info = cls._supported_hashalgos[hashfun_key] - except KeyError: - continue - - supported.append(((1, info.quality), (mechanism, info,))) - - if not supported: - return None - supported.sort() - - return supported.pop()[1] - - @classmethod - def parse_message( - cls, - msg: bytes, - ) -> typing.Generator[typing.Tuple[bytes, bytes], None, None]: - parts = ( - part - for part in msg.split(b",") - if part) - - for part in parts: - key, _, value = part.partition(b"=") - if len(key) > 1 or key == b"m": - raise Exception("SCRAM protocol violation / unknown " - "future extension") - if key == b"n" or key == b"a": - value = value.replace(b"=2C", b",").replace(b"=3D", b"=") - - yield key, value - - @abc.abstractmethod - def _get_gs2_header(self) -> bytes: - raise NotImplementedError - - @abc.abstractmethod - def _get_cb_data(self) -> bytes: - raise NotImplementedError - - async def authenticate( - self, - sm: statemachine.SASLStateMachine, - token: typing.Tuple[str, SCRAMHashInfo], - ) -> None: - mechanism, info, = token - logger.info("attempting %s mechanism (using %s hashfun)", - mechanism, - info) - # this is pretty much a verbatim implementation of RFC 5802. - - hashfun_factory = functools.partial(hashlib.new, info.hashfun_name) - - gs2_header = self._get_gs2_header() - username, password = await self._credential_provider() - encoded_username = stringprep.saslprep( - username, - allow_unassigned=True, - ).encode("utf-8") - encoded_password = stringprep.saslprep(password).encode("utf-8") - - our_nonce = base64.b64encode(_system_random.getrandbits( - self.nonce_length * 8 - ).to_bytes( - self.nonce_length, "little" - )) - - auth_message = b"n=" + encoded_username + b",r=" + our_nonce - state, payload = await sm.initiate( - mechanism, - gs2_header + auth_message) - - if state != common.SASLState.CHALLENGE or payload is None: - await sm.abort() - raise common.SASLFailure( - None, - text="protocol violation: expected challenge with payload") - - auth_message += b"," + payload - - parsed_payload = dict(self.parse_message(payload)) - - try: - iteration_count = int(parsed_payload[b"i"]) - nonce = parsed_payload[b"r"] - salt = base64.b64decode(parsed_payload[b"s"]) - except (ValueError, KeyError): - await sm.abort() - raise common.SASLFailure( - None, - text="malformed server message: {!r}".format(payload), - ) - - if not nonce.startswith(our_nonce): - await sm.abort() - raise common.SASLFailure( - None, - text="server nonce doesn't fit our nonce") - - if (self.enforce_minimum_iteration_count and - iteration_count < info.minimum_iteration_count): - raise common.SASLFailure( - None, - text="minimum iteration count for {} violated " - "({} is less than {})".format( - mechanism, - iteration_count, - info.minimum_iteration_count, - ) - ) - - t0 = time.time() - - salted_password = pbkdf2( - info.hashfun_name, - encoded_password, - salt, - iteration_count) - - logger.debug("pbkdf2 timing: %f seconds", time.time() - t0) - - client_key = hmac.new( - salted_password, - b"Client Key", - hashfun_factory).digest() - - stored_key = hashfun_factory(client_key).digest() - - reply = b"c=" + base64.b64encode(self._get_cb_data()) + b",r=" + nonce - - auth_message += b"," + reply - - client_proof = utils.xor_bytes( - hmac.new( - stored_key, - auth_message, - hashfun_factory).digest(), - client_key) - - logger.debug("response generation time: %f seconds", time.time() - t0) - try: - state, payload = await sm.response( - reply + b",p=" + base64.b64encode(client_proof) - ) - except common.SASLFailure as err: - raise err.promote_to_authentication_failure() from None - - # this is the pseudo-challenge for the server signature - # we have to reply with the empty string! - if state != common.SASLState.CHALLENGE: - raise common.SASLFailure( - "malformed-request", - text="SCRAM protocol violation") - - state, dummy_payload = await sm.response(b"") - if state != common.SASLState.SUCCESS or dummy_payload is not None: - raise common.SASLFailure( - None, - "SASL protocol violation") - - server_signature = hmac.new( - hmac.new( - salted_password, - b"Server Key", - hashfun_factory).digest(), - auth_message, - hashfun_factory).digest() - - parsed_payload = dict(self.parse_message(payload or b"")) - - if base64.b64decode(parsed_payload[b"v"]) != server_signature: - raise common.SASLFailure( - None, - "authentication successful, but server signature invalid", - ) - - -class SCRAM(Base, statemachine.SASLMechanism): - """ - The password-based SCRAM (non-PLUS) SASL mechanism (see :rfc:`5802`). - - :param credential_provider: A coroutine function which returns credentials. - :param after_scram_plus: Flag to indicate that SCRAM-PLUS *is* supported by - your implementation. - :type after_scram_plus: :class:`bool` - :param enforce_minimum_iteration_count: Enforce the minimum iteration - count specified by the SCRAM specifications. - :type enforce_minimum_iteration_count: :class:`bool` - - .. note:: - - As "non-PLUS" suggests, this does not support channel binding. - Use :class:`SCRAMPLUS` if you want channel binding. - - - `credential_provider` must be coroutine function which returns a ``(user, - password)`` tuple. - - If this is used after :class:`SCRAMPLUS` in a method list, the - keyword argument `after_scram_plus` should be set to - :data:`True`. Then we will use the gs2 header ``y,,`` to prevent - down-grade attacks by a man-in-the-middle attacker. - - `enforce_minimum_iteration_count` controls the enforcement of the specified - minimum iteration count for the key derivation function used in SCRAM. By - default, this enforcement is enabled, and you are strongly advised to not - disable it: it can be used to make the exchange weaker. - - Disabling `enforce_minimum_iteration_count` only makes sense if the - authentication exchange would otherwise fall back to using :class:`PLAIN` - or a similarly weak authentication mechanism. - - .. versionchanged:: 0.4 - - The `enforce_minimum_iteration_count` argument and the behaviour to - enforce the minimum iteration count by default was added. - """ - - def __init__( - self, - credential_provider: common.CredentialProvider, - *, - after_scram_plus: bool = False, - **kwargs: typing.Any): - super().__init__(credential_provider, **kwargs) - self._after_scram_plus = after_scram_plus - - def _get_gs2_header(self) -> bytes: - if self._after_scram_plus: - return b"y,," - else: - return b"n,," - - def _get_cb_data(self) -> bytes: - return self._get_gs2_header() - - -class SCRAMPLUS(Base, statemachine.SASLMechanism): - """ - The password-based SCRAM-PLUS SASL mechanism (see :rfc:`5802`). - - :param credential_provider: A coroutine function which returns credentials. - :param cb_provider: Object which provides channel binding data and - information. - :type cb_provider: :class:`.ChannelBindingProvider` - :param after_scram_plus: Flag to indicate that SCRAM-PLUS *is* supported by - your implementation. - :type after_scram_plus: :class:`bool` - :param enforce_minimum_iteration_count: Enforce the minimum iteration - count specified by the SCRAM specifications. - :type enforce_minimum_iteration_count: :class:`bool` - - `credential_provider` must be coroutine which returns a ``(user, - password)`` tuple. - - `cb_provider` must be an instance of - :class:`.ChannelBindingProvider`, which specifies and implements - the channel binding type to use. - - `enforce_minimum_iteration_count` controls the enforcement of the specified - minimum iteration count for the key derivation function used in SCRAM. By - default, this enforcement is enabled, and you are strongly advised to not - disable it: it can be used to make the exchange weaker. - - .. seealso:: - - :class:`SCRAM` for more information on - `enforce_minimum_iteration_count`. - - .. versionchanged:: 0.4 - - The `enforce_minimum_iteration_count` argument and the behaviour to - enforce the minimum iteration count by default was added. - - """ - _channel_binding = True - - def __init__(self, - credential_provider: common.CredentialProvider, - cb_provider: channel_binding.ChannelBindingProvider, - **kwargs: typing.Any): - super().__init__(credential_provider, **kwargs) - self._cb_provider = cb_provider - - def _get_gs2_header(self) -> bytes: - return b"p=" + self._cb_provider.cb_name + b",," - - def _get_cb_data(self) -> bytes: - gs2_header = self._get_gs2_header() - cb_data = self._cb_provider.extract_cb_data() - return gs2_header + cb_data diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/statemachine.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/statemachine.py deleted file mode 100644 index ea562ef..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/statemachine.py +++ /dev/null @@ -1,276 +0,0 @@ -######################################################################## -# File name: statemachine.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import abc -import typing - -from . import common - - -class SASLInterface(metaclass=abc.ABCMeta): - """ - This class serves as an abstract base class for interfaces for use with - :class:`SASLStateMachine`. Specific protocols using SASL (such as XMPP, - IMAP or SMTP) can subclass this interface to implement SASL on top of the - existing protocol. - - The interface class does not need to implement any state checking. State - checking is done by the :class:`SASLStateMachine`. The following interface - must be implemented by subclasses. - - The return values of the methods below are tuples of the following form: - - * ``(SASLState.SUCCESS, payload)`` -- After successful - authentication, success is returned. Depending on the mechanism, - a payload (as :class:`bytes` object) may be attached to the - result, otherwise, ``payload`` is :data:`None`. - - * ``(SASLState.CHALLENGE, payload)`` -- A challenge was sent by - the server in reply to the previous command. - - * ``(SASLState.FAILURE, None)`` -- This is only ever returned by - :meth:`abort`. All other methods **must** raise errors as - :class:`SASLFailure`. - - .. versionchanged:: 0.4 - - The first element of the returned tuples are now elements of - :class:`SASLState`. For compatibility with previous versions of - ``aiosasl`` the first elements of the string may be one of the - strings ``"success"``, ``"failure"`` or "``challenge``". For - more information see :meth:`SASLState.from_reply`. - - .. automethod:: initiate - - .. automethod:: respond - - .. automethod:: abort - """ - - @abc.abstractmethod - async def initiate( - self, - mechanism: str, - payload: typing.Optional[bytes] = None, - ) -> common.NextStateTuple: - """ - Send a SASL initiation request for the given `mechanism`. Depending on - the `mechanism`, an initial `payload` *may* be given. The `payload` is - then a :class:`bytes` object which needs to be passed as initial - payload during the initiation request. - - Wait for a reply by the peer and return the reply as a next-state tuple - in the format documented at :class:`SASLInterface`. - """ - - @abc.abstractmethod - async def respond( - self, - payload: bytes, - ) -> common.NextStateTuple: - """ - Send a response to a challenge. The `payload` is a :class:`bytes` - object which is to be sent as response. - - Wait for a reply by the peer and return the reply as a next-state tuple - in the format documented at :class:`SASLInterface`. - """ - - @abc.abstractmethod - async def abort(self) -> None: - """ - Abort the authentication. The result is either the failure tuple - (``(SASLState.FAILURE, None)``) or a :class:`SASLFailure` exception if - the response from the peer did not indicate abortion (e.g. another - error was returned by the peer or the peer indicated success). - """ - - -class SASLStateMachine: - """ - A state machine to reduce code duplication during SASL handshake. - - The state methods change the state and return the next client state of the - SASL handshake, optionally with server-supplied payload. - - Note that, with the notable exception of :meth:`abort`, ``failure`` states - are never returned but thrown as :class:`SASLFailure` instead. - - The initial state is never returned. - """ - - def __init__(self, interface: "SASLInterface"): - super().__init__() - self.interface = interface - self._state = common.SASLState.INITIAL - - async def initiate( - self, - mechanism: str, - payload: typing.Optional[bytes] = None, - ) -> common.NextStateTuple: - """ - Initiate the SASL handshake and advertise the use of the given - `mechanism`. If `payload` is not :data:`None`, it will be base64 - encoded and sent as initial client response along with the ```` - element. - - Return the next state of the state machine as tuple (see - :class:`SASLStateMachine` for details). - """ - - if self._state != common.SASLState.INITIAL: - raise RuntimeError("initiate has already been called") - - try: - next_state, payload = await self.interface.initiate( - mechanism, - payload=payload) - except common.SASLFailure: - self._state = common.SASLState.FAILURE - raise - - next_state = common.SASLState.from_reply(next_state) - self._state = next_state - return next_state, payload - - async def response( - self, - payload: bytes, - ) -> common.NextStateTuple: - """ - Send a response to the previously received challenge, with the given - `payload`. The payload is encoded using base64 and transmitted to the - server. - - Return the next state of the state machine as tuple (see - :class:`SASLStateMachine` for details). - """ - if self._state == common.SASLState.SUCCESS_SIMULATE_CHALLENGE: - if payload != b"": - # XXX: either our mechanism is buggy or the server - # sent SASLState.SUCCESS before all challenge-response - # messages defined by the mechanism were sent - self._state = common.SASLState.FAILURE - raise common.SASLFailure( - None, - "protocol violation: mechanism did not" - " respond with an empty response to a" - " challenge with final data – this suggests" - " a protocol-violating early success from the server." - ) - self._state = common.SASLState.SUCCESS - return common.SASLState.SUCCESS, None - - if self._state != common.SASLState.CHALLENGE: - raise RuntimeError( - "no challenge has been made or negotiation failed") - - try: - next_state, response_payload = await self.interface.respond( - payload, - ) - except common.SASLFailure: - self._state = common.SASLState.FAILURE - raise - - next_state = common.SASLState.from_reply(next_state) - - # unfold the (SASLState.SUCCESS, payload) to a sequence of - # (SASLState.CHALLENGE, payload), (SASLState.SUCCESS, None) for the - # SASLMethod to allow uniform treatment of both cases - if (next_state == common.SASLState.SUCCESS and - response_payload is not None): - self._state = common.SASLState.SUCCESS_SIMULATE_CHALLENGE - return common.SASLState.CHALLENGE, response_payload - - self._state = next_state - return next_state, response_payload - - async def abort(self) -> None: - """ - Abort an initiated SASL authentication process. The expected result - state is ``failure``. - """ - if self._state == common.SASLState.INITIAL: - raise RuntimeError("SASL authentication hasn't started yet") - - if self._state == common.SASLState.SUCCESS_SIMULATE_CHALLENGE: - raise RuntimeError("SASL message exchange already over") - - try: - return await self.interface.abort() - finally: - self._state = common.SASLState.FAILURE - - -class SASLMechanism(metaclass=abc.ABCMeta): - """ - Implementation of a SASL mechanism. Two methods must be implemented by - subclasses: - - .. automethod:: any_supported - - .. automethod:: authenticate - - .. note:: Administrative note - - Patches for new SASL mechanisms are welcome! - - """ - - @abc.abstractclassmethod - def any_supported( - cls, - mechanisms: typing.Iterable[str], - ) -> typing.Any: - """ - Determine whether this class can perform any SASL mechanism in the set - of strings ``mechanisms``. - - If the class cannot perform any of the SASL mechanisms in - ``mechanisms``, it must return :data:`None`. - - Otherwise, it must return a non-:data:`None` value. Applications must - not assign any meaning to any value (except that :data:`None` is a sure - indicator that the class cannot perform any of the listed mechanisms) - and must not alter any value returned by this function. Note that even - :data:`False` indicates success! - - The return value must be passed as second argument to - :meth:`authenticate`. :meth:`authenticate` must not be called with a - :data:`None` value. - """ - - async def authenticate( - self, - sm: SASLStateMachine, - token: typing.Any, - ) -> None: - """ - Execute the mechanism identified by `token` (the non-:data:`None` value - which has been returned by :meth:`any_supported` before) using the - given :class:`SASLStateMachine` `sm`. - - If authentication fails, an appropriate exception is raised - (:class:`AuthenticationFailure`). If the authentication fails for a - reason unrelated to credentials, :class:`SASLFailure` is raised. - """ diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/stringprep.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/stringprep.py deleted file mode 100644 index 871aa06..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/stringprep.py +++ /dev/null @@ -1,213 +0,0 @@ -######################################################################## -# File name: stringprep.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -Stringprep support -################## - -This module implements the SASLprep (`RFC 4013`_) stringprep profile. - -.. autofunction:: saslprep - -.. _RFC 4013: https://tools.ietf.org/html/rfc4013 - -""" - -import stringprep -import typing - -from unicodedata import ucd_3_2_0 as unicodedata - -_nodeprep_prohibited = frozenset("\"&'/:<>@") - - -def is_RandALCat(c: str) -> bool: - return unicodedata.bidirectional(c) in ("R", "AL") - - -def is_LCat(c: str) -> bool: - return unicodedata.bidirectional(c) == "L" - - -TablePredicate = typing.Callable[[str], bool] - - -def check_against_tables( - chars: typing.Iterable[str], - tables: typing.Iterable[TablePredicate], - ) -> typing.Optional[str]: - """ - Perform a check against the table predicates in `tables`. `tables` must be - a reusable iterable containing characteristic functions of character sets, - that is, functions which return :data:`True` if the character is in the - table. - - The function returns the first character occuring in any of the tables or - :data:`None` if no character matches. - """ - - for c in chars: - if any(in_table(c) for in_table in tables): - return c - - return None - - -def do_normalization(chars: typing.MutableSequence[str]) -> None: - """ - Perform the stringprep normalization. Operates in-place on a list of - unicode characters provided in `chars`. - """ - chars[:] = list(unicodedata.normalize("NFKC", "".join(chars))) - - -def check_bidi(chars: typing.Sequence[str]) -> None: - """ - Check proper bidirectionality as per stringprep. Operates on a list of - unicode characters provided in `chars`. - """ - - # the empty string is valid, as it cannot violate the RandALCat constraints - if not chars: - return - - # first_is_RorAL = unicodedata.bidirectional(chars[0]) in {"R", "AL"} - # if first_is_RorAL: - - has_RandALCat = any(is_RandALCat(c) for c in chars) - if not has_RandALCat: - return - - has_LCat = any(is_LCat(c) for c in chars) - if has_LCat: - raise ValueError("L and R/AL characters must not occur in the same" - " string") - - if not is_RandALCat(chars[0]) or not is_RandALCat(chars[-1]): - raise ValueError("R/AL string must start and end with R/AL character.") - - -def check_prohibited_output( - chars: typing.Sequence[str], - bad_tables: typing.Iterable[TablePredicate]) -> None: - """ - Check against prohibited output, by checking whether any of the characters - from `chars` are in any of the `bad_tables`. - - Operates in-place on a list of code points from `chars`. - """ - violator = check_against_tables(chars, bad_tables) - if violator is not None: - raise ValueError("Input contains invalid unicode codepoint: " - "U+{:04x}".format(ord(violator))) - - -def check_unassigned(chars: typing.Sequence[str], - bad_tables: typing.Iterable[TablePredicate]) -> None: - """ - Check that `chars` does not contain any unassigned code points as per - the given list of `bad_tables`. - - Operates on a list of unicode code points provided in `chars`. - """ - bad_tables = ( - stringprep.in_table_a1,) - - violator = check_against_tables(chars, bad_tables) - if violator is not None: - raise ValueError("Input contains unassigned code point: " - "U+{:04x}".format(ord(violator))) - - -def _saslprep_do_mapping(chars: typing.MutableSequence[str]) -> None: - """ - Perform the stringprep mapping step of SASLprep. Operates in-place on a - list of unicode characters provided in `chars`. - """ - i = 0 - while i < len(chars): - c = chars[i] - if stringprep.in_table_c12(c): - chars[i] = "\u0020" - elif stringprep.in_table_b1(c): - del chars[i] - continue - i += 1 - - -def saslprep(string: str, - allow_unassigned: bool = False) -> str: - """ - Process the given `string` using the SASLprep profile. In the error cases - defined in `RFC 3454`_ (stringprep), a :class:`ValueError` is raised. - """ - - chars = list(string) - _saslprep_do_mapping(chars) - do_normalization(chars) - check_prohibited_output( - chars, - ( - stringprep.in_table_c12, - stringprep.in_table_c21, - stringprep.in_table_c22, - stringprep.in_table_c3, - stringprep.in_table_c4, - stringprep.in_table_c5, - stringprep.in_table_c6, - stringprep.in_table_c7, - stringprep.in_table_c8, - stringprep.in_table_c9 - ) - ) - check_bidi(chars) - - if not allow_unassigned: - check_unassigned( - chars, - ( - stringprep.in_table_a1, - ) - ) - - return "".join(chars) - - -def trace(string: str) -> str: - """ - Implement the ``trace`` profile specified in :rfc:`4505`. - """ - - check_prohibited_output( - string, - ( - stringprep.in_table_c21, - stringprep.in_table_c22, - stringprep.in_table_c3, - stringprep.in_table_c4, - stringprep.in_table_c5, - stringprep.in_table_c6, - stringprep.in_table_c8, - stringprep.in_table_c9, - ) - ) - check_bidi(string) - return string diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/utils.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/utils.py deleted file mode 100644 index 237903a..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/utils.py +++ /dev/null @@ -1,32 +0,0 @@ -######################################################################## -# File name: utils.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## - -import operator - - -def xor_bytes(a: bytes, b: bytes) -> bytes: - """ - Calculate the byte wise exclusive of of two :class:`bytes` objects - of the same length. - """ - assert len(a) == len(b) - return bytes(map(operator.xor, a, b)) diff --git a/tests/venv2/lib/python3.11/site-packages/aiosasl/version.py b/tests/venv2/lib/python3.11/site-packages/aiosasl/version.py deleted file mode 100644 index eb9c1d3..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aiosasl/version.py +++ /dev/null @@ -1,30 +0,0 @@ -######################################################################## -# File name: version.py -# This file is part of: aiosasl -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -version_info = (0, 5, 0, None) - -__version__ = ".".join(map(str, version_info[:3])) + ( - "-"+version_info[3] # type:ignore - if version_info[3] is not None # type:ignore - else "" -) - -version = __version__ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/PKG-INFO b/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/PKG-INFO deleted file mode 100644 index 4d57f2f..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/PKG-INFO +++ /dev/null @@ -1,184 +0,0 @@ -Metadata-Version: 2.1 -Name: aioxmpp -Version: 0.13.3 -Summary: Pure-python XMPP library for asyncio -Home-page: https://github.com/horazont/aioxmpp -Author: Jonas Schäfer -Author-email: jonas@wielicki.name -License: LGPLv3+ -Keywords: asyncio xmpp library -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Operating System :: POSIX -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Topic :: Communications :: Chat -Classifier: Topic :: Internet :: XMPP -License-File: LICENSES -License-File: COPYING.LESSER -License-File: COPYING.gpl3 - -``aioxmpp`` -########### - -.. image:: https://travis-ci.org/horazont/aioxmpp.svg?branch=devel - :target: https://travis-ci.org/horazont/aioxmpp - -.. image:: https://coveralls.io/repos/github/horazont/aioxmpp/badge.svg?branch=devel - :target: https://coveralls.io/github/horazont/aioxmpp?branch=devel - -.. image:: https://img.shields.io/pypi/v/aioxmpp.svg - :target: https://pypi.python.org/pypi/aioxmpp/ - -... is a pure-python XMPP library using the `asyncio`_ standard library module from Python 3.4 (and `available as a third-party module to Python 3.3`__). - -.. _asyncio: https://docs.python.org/3/library/asyncio.html -__ https://code.google.com/p/tulip/ - -.. remember to update the feature list in the docs - -Features -======== - -* Native `Stream Management (XEP-0198) - `_ support for robustness against - transient network failures (such as switching between wireless and wired - networks). - -* Powerful declarative-style definition of XEP-based and custom protocols. Most - of the time, you will not get in contact with raw XML or character data, even - when implementing a new protocol. - -* Secure by default: TLS is required by default, as well as certificate - validation. Certificate or public key pinning can be used, if needed. - -* Support for `RFC 6121 (Instant Messaging and Presence) - `_ roster and presence management, along - with `XEP-0045 (Multi-User Chats) - `_ for your human-to-human needs. - -* Support for `XEP-0060 (Publish-Subscribe) - `_ and `XEP-0050 (Ad-Hoc Commands) - `_ for your machine-to-machine - needs. - -* Several other XEPs, such as `XEP-0115 - `_ (including native support for - the reading and writing the `capsdb `_) and - `XEP-0131 `_. - -* APIs suitable for both one-shot scripts and long-running multi-account - clients. - -* Well-tested and modular codebase: aioxmpp is developed in test-driven - style and in addition to that, many modules are automatedly tested against - `Prosody `_ and `ejabberd `_, - two popular XMPP servers. - - -There is more and there’s yet more to come! Check out the list of supported XEPs -in the `official documentation`_ and `open GitHub issues tagged as enhancement -`_ -for things which are planned and read on below on how to contribute. - -Documentation -============= - -The ``aioxmpp`` API is thoroughly documented using Sphinx. Check out the `official documentation`_ for a `quick start`_ and the `API reference`_. - -Dependencies -============ - -* Python ≥ 3.4 (or Python = 3.3 with tulip and enum34) -* DNSPython -* lxml -* `sortedcollections`__ - - __ https://pypi.python.org/pypi/sortedcollections - -* `tzlocal`__ (for i18n support) - - __ https://pypi.python.org/pypi/tzlocal - -* `pyOpenSSL`__ - - __ https://pypi.python.org/pypi/pyOpenSSL - -* `pyasn1`_ and `pyasn1_modules`__ - - .. _pyasn1: https://pypi.python.org/pypi/pyasn1 - __ https://pypi.python.org/pypi/pyasn1-modules - -* `aiosasl`__ (≥ 0.3 for ``ANONYMOUS`` support) - - __ https://pypi.python.org/pypi/aiosasl - -* `multidict`__ - - __ https://pypi.python.org/pypi/multidict - -* `aioopenssl`__ - - __ https://github.com/horazont/aioopenssl - -* `typing`__ (Python < 3.5 only) - - __ https://pypi.python.org/pypi/typing - -Contributing -============ - -If you consider contributing to aioxmpp, you can do so, even without a GitHub -account. There are several ways to get in touch with the aioxmpp developer(s): - -* `The development mailing list - `_. Feel - free to subscribe and post, but be polite and adhere to the `Netiquette - (RFC 1855) `_. Pull requests posted to - the mailing list are also welcome! - -* The development MUC at ``aioxmpp@conference.zombofant.net``. Pull requests - announced in the MUC are also welcome! Note that the MUC is set persistent, - but nevertheless there may not always be people around. If in doubt, use the - mailing list instead. - -* Open or comment on an issue or post a pull request on `GitHub - `_. - -No idea what to do, but still want to get your hands dirty? Check out the list -of `'help wanted' issues on GitHub -`_ -or ask in the MUC or on the mailing list. The issues tagged as 'help wanted' are -usually of narrow scope, aimed at beginners. - -Be sure to read the ``docs/CONTRIBUTING.rst`` for some hints on how to -author your contribution. - -Security issues ---------------- - -If you believe that a bug you found in aioxmpp has security implications, -you are welcome to notify me privately. To do so, send a mail to `Jonas Schäfer -`_, encrypted using the GPG public key -0xE5EDE5AC679E300F (Fingerprint AA5A 78FF 508D 8CF4 F355 F682 E5ED E5AC 679E -300F). - -If you prefer to disclose security issues immediately, you can do so at any of -the places listed above. - -More details can be found in the `SECURITY.md `_ file. - -Change log -========== - -The `change log`_ is included in the `official documentation`_. - -.. _change log: https://docs.zombofant.net/aioxmpp/0.13/api/changelog.html -.. _official documentation: https://docs.zombofant.net/aioxmpp/0.13/ -.. _quick start: https://docs.zombofant.net/aioxmpp/0.13/user-guide/quickstart.html -.. _API reference: https://docs.zombofant.net/aioxmpp/0.13/api/index.html diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/SOURCES.txt b/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/SOURCES.txt deleted file mode 100644 index 2d2b7ed..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/SOURCES.txt +++ /dev/null @@ -1,149 +0,0 @@ -COPYING.LESSER -COPYING.gpl3 -LICENSES -MANIFEST.in -README.rst -setup.cfg -setup.py -aioxmpp/__init__.py -aioxmpp/_version.py -aioxmpp/cache.py -aioxmpp/callbacks.py -aioxmpp/connector.py -aioxmpp/custom_queue.py -aioxmpp/dispatcher.py -aioxmpp/errors.py -aioxmpp/hashes.py -aioxmpp/i18n.py -aioxmpp/network.py -aioxmpp/node.py -aioxmpp/nonza.py -aioxmpp/protocol.py -aioxmpp/rfc3921.py -aioxmpp/rfc6120.py -aioxmpp/sasl.py -aioxmpp/security_layer.py -aioxmpp/service.py -aioxmpp/ssl_transport.py -aioxmpp/stanza.py -aioxmpp/statemachine.py -aioxmpp/stream.py -aioxmpp/stringprep.py -aioxmpp/structs.py -aioxmpp/tasks.py -aioxmpp/testutils.py -aioxmpp/tracking.py -aioxmpp/utils.py -aioxmpp/xml.py -aioxmpp/xmltestutils.py -aioxmpp.egg-info/PKG-INFO -aioxmpp.egg-info/SOURCES.txt -aioxmpp.egg-info/dependency_links.txt -aioxmpp.egg-info/requires.txt -aioxmpp.egg-info/top_level.txt -aioxmpp/adhoc/__init__.py -aioxmpp/adhoc/service.py -aioxmpp/adhoc/xso.py -aioxmpp/avatar/__init__.py -aioxmpp/avatar/service.py -aioxmpp/avatar/xso.py -aioxmpp/benchtest/__init__.py -aioxmpp/benchtest/__main__.py -aioxmpp/blocking/__init__.py -aioxmpp/blocking/service.py -aioxmpp/blocking/xso.py -aioxmpp/bookmarks/__init__.py -aioxmpp/bookmarks/service.py -aioxmpp/bookmarks/xso.py -aioxmpp/carbons/__init__.py -aioxmpp/carbons/service.py -aioxmpp/carbons/xso.py -aioxmpp/chatstates/__init__.py -aioxmpp/chatstates/utils.py -aioxmpp/chatstates/xso.py -aioxmpp/disco/__init__.py -aioxmpp/disco/service.py -aioxmpp/disco/xso.py -aioxmpp/e2etest/__init__.py -aioxmpp/e2etest/__main__.py -aioxmpp/e2etest/provision.py -aioxmpp/e2etest/utils.py -aioxmpp/entitycaps/__init__.py -aioxmpp/entitycaps/caps115.py -aioxmpp/entitycaps/caps390.py -aioxmpp/entitycaps/common.py -aioxmpp/entitycaps/service.py -aioxmpp/entitycaps/xso.py -aioxmpp/forms/__init__.py -aioxmpp/forms/fields.py -aioxmpp/forms/form.py -aioxmpp/forms/xso.py -aioxmpp/httpupload/__init__.py -aioxmpp/httpupload/xso.py -aioxmpp/ibb/__init__.py -aioxmpp/ibb/service.py -aioxmpp/ibb/xso.py -aioxmpp/ibr/__init__.py -aioxmpp/ibr/service.py -aioxmpp/ibr/xso.py -aioxmpp/im/__init__.py -aioxmpp/im/body.py -aioxmpp/im/conversation.py -aioxmpp/im/dispatcher.py -aioxmpp/im/muc.py -aioxmpp/im/p2p.py -aioxmpp/im/service.py -aioxmpp/mdr/__init__.py -aioxmpp/mdr/service.py -aioxmpp/mdr/xso.py -aioxmpp/misc/__init__.py -aioxmpp/misc/delay.py -aioxmpp/misc/forwarding.py -aioxmpp/misc/json.py -aioxmpp/misc/lmc.py -aioxmpp/misc/markers.py -aioxmpp/misc/oob.py -aioxmpp/misc/openpgp_legacy.py -aioxmpp/misc/pars.py -aioxmpp/misc/stanzaid.py -aioxmpp/muc/__init__.py -aioxmpp/muc/self_ping.py -aioxmpp/muc/service.py -aioxmpp/muc/xso.py -aioxmpp/pep/__init__.py -aioxmpp/pep/service.py -aioxmpp/ping/__init__.py -aioxmpp/ping/service.py -aioxmpp/ping/xso.py -aioxmpp/presence/__init__.py -aioxmpp/presence/service.py -aioxmpp/private_xml/__init__.py -aioxmpp/private_xml/service.py -aioxmpp/private_xml/xso.py -aioxmpp/pubsub/__init__.py -aioxmpp/pubsub/service.py -aioxmpp/pubsub/xso.py -aioxmpp/roster/__init__.py -aioxmpp/roster/service.py -aioxmpp/roster/xso.py -aioxmpp/rsm/__init__.py -aioxmpp/rsm/xso.py -aioxmpp/shim/__init__.py -aioxmpp/shim/service.py -aioxmpp/shim/xso.py -aioxmpp/vcard/__init__.py -aioxmpp/vcard/service.py -aioxmpp/vcard/xso.py -aioxmpp/version/__init__.py -aioxmpp/version/service.py -aioxmpp/version/xso.py -aioxmpp/xso/__init__.py -aioxmpp/xso/model.py -aioxmpp/xso/query.py -aioxmpp/xso/types.py -docs/licenses/apache20.txt -docs/licenses/dnspython.txt -docs/licenses/libxml2.txt -docs/licenses/lxml.txt -docs/licenses/orderedset.txt -docs/licenses/pyasn1.txt \ No newline at end of file diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/dependency_links.txt b/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/installed-files.txt b/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/installed-files.txt deleted file mode 100644 index 7ec5835..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/installed-files.txt +++ /dev/null @@ -1,267 +0,0 @@ -../aioxmpp/__init__.py -../aioxmpp/__pycache__/__init__.cpython-311.pyc -../aioxmpp/__pycache__/_version.cpython-311.pyc -../aioxmpp/__pycache__/cache.cpython-311.pyc -../aioxmpp/__pycache__/callbacks.cpython-311.pyc -../aioxmpp/__pycache__/connector.cpython-311.pyc -../aioxmpp/__pycache__/custom_queue.cpython-311.pyc -../aioxmpp/__pycache__/dispatcher.cpython-311.pyc -../aioxmpp/__pycache__/errors.cpython-311.pyc -../aioxmpp/__pycache__/hashes.cpython-311.pyc -../aioxmpp/__pycache__/i18n.cpython-311.pyc -../aioxmpp/__pycache__/network.cpython-311.pyc -../aioxmpp/__pycache__/node.cpython-311.pyc -../aioxmpp/__pycache__/nonza.cpython-311.pyc -../aioxmpp/__pycache__/protocol.cpython-311.pyc -../aioxmpp/__pycache__/rfc3921.cpython-311.pyc -../aioxmpp/__pycache__/rfc6120.cpython-311.pyc -../aioxmpp/__pycache__/sasl.cpython-311.pyc -../aioxmpp/__pycache__/security_layer.cpython-311.pyc -../aioxmpp/__pycache__/service.cpython-311.pyc -../aioxmpp/__pycache__/ssl_transport.cpython-311.pyc -../aioxmpp/__pycache__/stanza.cpython-311.pyc -../aioxmpp/__pycache__/statemachine.cpython-311.pyc -../aioxmpp/__pycache__/stream.cpython-311.pyc -../aioxmpp/__pycache__/stringprep.cpython-311.pyc -../aioxmpp/__pycache__/structs.cpython-311.pyc -../aioxmpp/__pycache__/tasks.cpython-311.pyc -../aioxmpp/__pycache__/testutils.cpython-311.pyc -../aioxmpp/__pycache__/tracking.cpython-311.pyc -../aioxmpp/__pycache__/utils.cpython-311.pyc -../aioxmpp/__pycache__/xml.cpython-311.pyc -../aioxmpp/__pycache__/xmltestutils.cpython-311.pyc -../aioxmpp/_version.py -../aioxmpp/adhoc/__init__.py -../aioxmpp/adhoc/__pycache__/__init__.cpython-311.pyc -../aioxmpp/adhoc/__pycache__/service.cpython-311.pyc -../aioxmpp/adhoc/__pycache__/xso.cpython-311.pyc -../aioxmpp/adhoc/service.py -../aioxmpp/adhoc/xso.py -../aioxmpp/avatar/__init__.py -../aioxmpp/avatar/__pycache__/__init__.cpython-311.pyc -../aioxmpp/avatar/__pycache__/service.cpython-311.pyc -../aioxmpp/avatar/__pycache__/xso.cpython-311.pyc -../aioxmpp/avatar/service.py -../aioxmpp/avatar/xso.py -../aioxmpp/benchtest/__init__.py -../aioxmpp/benchtest/__main__.py -../aioxmpp/benchtest/__pycache__/__init__.cpython-311.pyc -../aioxmpp/benchtest/__pycache__/__main__.cpython-311.pyc -../aioxmpp/blocking/__init__.py -../aioxmpp/blocking/__pycache__/__init__.cpython-311.pyc -../aioxmpp/blocking/__pycache__/service.cpython-311.pyc -../aioxmpp/blocking/__pycache__/xso.cpython-311.pyc -../aioxmpp/blocking/service.py -../aioxmpp/blocking/xso.py -../aioxmpp/bookmarks/__init__.py -../aioxmpp/bookmarks/__pycache__/__init__.cpython-311.pyc -../aioxmpp/bookmarks/__pycache__/service.cpython-311.pyc -../aioxmpp/bookmarks/__pycache__/xso.cpython-311.pyc -../aioxmpp/bookmarks/service.py -../aioxmpp/bookmarks/xso.py -../aioxmpp/cache.py -../aioxmpp/callbacks.py -../aioxmpp/carbons/__init__.py -../aioxmpp/carbons/__pycache__/__init__.cpython-311.pyc -../aioxmpp/carbons/__pycache__/service.cpython-311.pyc -../aioxmpp/carbons/__pycache__/xso.cpython-311.pyc -../aioxmpp/carbons/service.py -../aioxmpp/carbons/xso.py -../aioxmpp/chatstates/__init__.py -../aioxmpp/chatstates/__pycache__/__init__.cpython-311.pyc -../aioxmpp/chatstates/__pycache__/utils.cpython-311.pyc -../aioxmpp/chatstates/__pycache__/xso.cpython-311.pyc -../aioxmpp/chatstates/utils.py -../aioxmpp/chatstates/xso.py -../aioxmpp/connector.py -../aioxmpp/custom_queue.py -../aioxmpp/disco/__init__.py -../aioxmpp/disco/__pycache__/__init__.cpython-311.pyc -../aioxmpp/disco/__pycache__/service.cpython-311.pyc -../aioxmpp/disco/__pycache__/xso.cpython-311.pyc -../aioxmpp/disco/service.py -../aioxmpp/disco/xso.py -../aioxmpp/dispatcher.py -../aioxmpp/e2etest/__init__.py -../aioxmpp/e2etest/__main__.py -../aioxmpp/e2etest/__pycache__/__init__.cpython-311.pyc -../aioxmpp/e2etest/__pycache__/__main__.cpython-311.pyc -../aioxmpp/e2etest/__pycache__/provision.cpython-311.pyc -../aioxmpp/e2etest/__pycache__/utils.cpython-311.pyc -../aioxmpp/e2etest/provision.py -../aioxmpp/e2etest/utils.py -../aioxmpp/entitycaps/__init__.py -../aioxmpp/entitycaps/__pycache__/__init__.cpython-311.pyc -../aioxmpp/entitycaps/__pycache__/caps115.cpython-311.pyc -../aioxmpp/entitycaps/__pycache__/caps390.cpython-311.pyc -../aioxmpp/entitycaps/__pycache__/common.cpython-311.pyc -../aioxmpp/entitycaps/__pycache__/service.cpython-311.pyc -../aioxmpp/entitycaps/__pycache__/xso.cpython-311.pyc -../aioxmpp/entitycaps/caps115.py -../aioxmpp/entitycaps/caps390.py -../aioxmpp/entitycaps/common.py -../aioxmpp/entitycaps/service.py -../aioxmpp/entitycaps/xso.py -../aioxmpp/errors.py -../aioxmpp/forms/__init__.py -../aioxmpp/forms/__pycache__/__init__.cpython-311.pyc -../aioxmpp/forms/__pycache__/fields.cpython-311.pyc -../aioxmpp/forms/__pycache__/form.cpython-311.pyc -../aioxmpp/forms/__pycache__/xso.cpython-311.pyc -../aioxmpp/forms/fields.py -../aioxmpp/forms/form.py -../aioxmpp/forms/xso.py -../aioxmpp/hashes.py -../aioxmpp/httpupload/__init__.py -../aioxmpp/httpupload/__pycache__/__init__.cpython-311.pyc -../aioxmpp/httpupload/__pycache__/xso.cpython-311.pyc -../aioxmpp/httpupload/xso.py -../aioxmpp/i18n.py -../aioxmpp/ibb/__init__.py -../aioxmpp/ibb/__pycache__/__init__.cpython-311.pyc -../aioxmpp/ibb/__pycache__/service.cpython-311.pyc -../aioxmpp/ibb/__pycache__/xso.cpython-311.pyc -../aioxmpp/ibb/service.py -../aioxmpp/ibb/xso.py -../aioxmpp/ibr/__init__.py -../aioxmpp/ibr/__pycache__/__init__.cpython-311.pyc -../aioxmpp/ibr/__pycache__/service.cpython-311.pyc -../aioxmpp/ibr/__pycache__/xso.cpython-311.pyc -../aioxmpp/ibr/service.py -../aioxmpp/ibr/xso.py -../aioxmpp/im/__init__.py -../aioxmpp/im/__pycache__/__init__.cpython-311.pyc -../aioxmpp/im/__pycache__/body.cpython-311.pyc -../aioxmpp/im/__pycache__/conversation.cpython-311.pyc -../aioxmpp/im/__pycache__/dispatcher.cpython-311.pyc -../aioxmpp/im/__pycache__/muc.cpython-311.pyc -../aioxmpp/im/__pycache__/p2p.cpython-311.pyc -../aioxmpp/im/__pycache__/service.cpython-311.pyc -../aioxmpp/im/body.py -../aioxmpp/im/conversation.py -../aioxmpp/im/dispatcher.py -../aioxmpp/im/muc.py -../aioxmpp/im/p2p.py -../aioxmpp/im/service.py -../aioxmpp/mdr/__init__.py -../aioxmpp/mdr/__pycache__/__init__.cpython-311.pyc -../aioxmpp/mdr/__pycache__/service.cpython-311.pyc -../aioxmpp/mdr/__pycache__/xso.cpython-311.pyc -../aioxmpp/mdr/service.py -../aioxmpp/mdr/xso.py -../aioxmpp/misc/__init__.py -../aioxmpp/misc/__pycache__/__init__.cpython-311.pyc -../aioxmpp/misc/__pycache__/delay.cpython-311.pyc -../aioxmpp/misc/__pycache__/forwarding.cpython-311.pyc -../aioxmpp/misc/__pycache__/json.cpython-311.pyc -../aioxmpp/misc/__pycache__/lmc.cpython-311.pyc -../aioxmpp/misc/__pycache__/markers.cpython-311.pyc -../aioxmpp/misc/__pycache__/oob.cpython-311.pyc -../aioxmpp/misc/__pycache__/openpgp_legacy.cpython-311.pyc -../aioxmpp/misc/__pycache__/pars.cpython-311.pyc -../aioxmpp/misc/__pycache__/stanzaid.cpython-311.pyc -../aioxmpp/misc/delay.py -../aioxmpp/misc/forwarding.py -../aioxmpp/misc/json.py -../aioxmpp/misc/lmc.py -../aioxmpp/misc/markers.py -../aioxmpp/misc/oob.py -../aioxmpp/misc/openpgp_legacy.py -../aioxmpp/misc/pars.py -../aioxmpp/misc/stanzaid.py -../aioxmpp/muc/__init__.py -../aioxmpp/muc/__pycache__/__init__.cpython-311.pyc -../aioxmpp/muc/__pycache__/self_ping.cpython-311.pyc -../aioxmpp/muc/__pycache__/service.cpython-311.pyc -../aioxmpp/muc/__pycache__/xso.cpython-311.pyc -../aioxmpp/muc/self_ping.py -../aioxmpp/muc/service.py -../aioxmpp/muc/xso.py -../aioxmpp/network.py -../aioxmpp/node.py -../aioxmpp/nonza.py -../aioxmpp/pep/__init__.py -../aioxmpp/pep/__pycache__/__init__.cpython-311.pyc -../aioxmpp/pep/__pycache__/service.cpython-311.pyc -../aioxmpp/pep/service.py -../aioxmpp/ping/__init__.py -../aioxmpp/ping/__pycache__/__init__.cpython-311.pyc -../aioxmpp/ping/__pycache__/service.cpython-311.pyc -../aioxmpp/ping/__pycache__/xso.cpython-311.pyc -../aioxmpp/ping/service.py -../aioxmpp/ping/xso.py -../aioxmpp/presence/__init__.py -../aioxmpp/presence/__pycache__/__init__.cpython-311.pyc -../aioxmpp/presence/__pycache__/service.cpython-311.pyc -../aioxmpp/presence/service.py -../aioxmpp/private_xml/__init__.py -../aioxmpp/private_xml/__pycache__/__init__.cpython-311.pyc -../aioxmpp/private_xml/__pycache__/service.cpython-311.pyc -../aioxmpp/private_xml/__pycache__/xso.cpython-311.pyc -../aioxmpp/private_xml/service.py -../aioxmpp/private_xml/xso.py -../aioxmpp/protocol.py -../aioxmpp/pubsub/__init__.py -../aioxmpp/pubsub/__pycache__/__init__.cpython-311.pyc -../aioxmpp/pubsub/__pycache__/service.cpython-311.pyc -../aioxmpp/pubsub/__pycache__/xso.cpython-311.pyc -../aioxmpp/pubsub/service.py -../aioxmpp/pubsub/xso.py -../aioxmpp/rfc3921.py -../aioxmpp/rfc6120.py -../aioxmpp/roster/__init__.py -../aioxmpp/roster/__pycache__/__init__.cpython-311.pyc -../aioxmpp/roster/__pycache__/service.cpython-311.pyc -../aioxmpp/roster/__pycache__/xso.cpython-311.pyc -../aioxmpp/roster/service.py -../aioxmpp/roster/xso.py -../aioxmpp/rsm/__init__.py -../aioxmpp/rsm/__pycache__/__init__.cpython-311.pyc -../aioxmpp/rsm/__pycache__/xso.cpython-311.pyc -../aioxmpp/rsm/xso.py -../aioxmpp/sasl.py -../aioxmpp/security_layer.py -../aioxmpp/service.py -../aioxmpp/shim/__init__.py -../aioxmpp/shim/__pycache__/__init__.cpython-311.pyc -../aioxmpp/shim/__pycache__/service.cpython-311.pyc -../aioxmpp/shim/__pycache__/xso.cpython-311.pyc -../aioxmpp/shim/service.py -../aioxmpp/shim/xso.py -../aioxmpp/ssl_transport.py -../aioxmpp/stanza.py -../aioxmpp/statemachine.py -../aioxmpp/stream.py -../aioxmpp/stringprep.py -../aioxmpp/structs.py -../aioxmpp/tasks.py -../aioxmpp/testutils.py -../aioxmpp/tracking.py -../aioxmpp/utils.py -../aioxmpp/vcard/__init__.py -../aioxmpp/vcard/__pycache__/__init__.cpython-311.pyc -../aioxmpp/vcard/__pycache__/service.cpython-311.pyc -../aioxmpp/vcard/__pycache__/xso.cpython-311.pyc -../aioxmpp/vcard/service.py -../aioxmpp/vcard/xso.py -../aioxmpp/version/__init__.py -../aioxmpp/version/__pycache__/__init__.cpython-311.pyc -../aioxmpp/version/__pycache__/service.cpython-311.pyc -../aioxmpp/version/__pycache__/xso.cpython-311.pyc -../aioxmpp/version/service.py -../aioxmpp/version/xso.py -../aioxmpp/xml.py -../aioxmpp/xmltestutils.py -../aioxmpp/xso/__init__.py -../aioxmpp/xso/__pycache__/__init__.cpython-311.pyc -../aioxmpp/xso/__pycache__/model.cpython-311.pyc -../aioxmpp/xso/__pycache__/query.cpython-311.pyc -../aioxmpp/xso/__pycache__/types.cpython-311.pyc -../aioxmpp/xso/model.py -../aioxmpp/xso/query.py -../aioxmpp/xso/types.py -PKG-INFO -SOURCES.txt -dependency_links.txt -requires.txt -top_level.txt diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/requires.txt b/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/requires.txt deleted file mode 100644 index e9d32b5..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/requires.txt +++ /dev/null @@ -1,11 +0,0 @@ -aioopenssl>=0.1 -aiosasl>=0.3 -babel~=2.3 -dnspython<3.0,>=1.0 -lxml~=4.0 -multidict<7,>=2.0 -pyOpenSSL -pyasn1 -pyasn1_modules -sortedcollections~=2.1 -tzlocal>=1.2 diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/top_level.txt b/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/top_level.txt deleted file mode 100644 index f590b0e..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp-0.13.3.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -aioxmpp diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__init__.py deleted file mode 100644 index 16738ed..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__init__.py +++ /dev/null @@ -1,154 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -Version information -################### - -There are two ways to obtain the imported version of the :mod:`aioxmpp` -package: - -.. autodata:: __version__ - -.. data:: version - - Alias of :data:`__version__`. - -.. autodata:: version_info - -.. _api-aioxmpp-services: - -Overview of Services -#################### - -.. autosummary:: - :nosignatures: - - aioxmpp.AdHocClient - aioxmpp.AvatarService - aioxmpp.BlockingClient - aioxmpp.BookmarkClient - aioxmpp.CarbonsClient - aioxmpp.DiscoClient - aioxmpp.DiscoServer - aioxmpp.EntityCapsService - aioxmpp.MUCClient - aioxmpp.PingService - aioxmpp.PresenceClient - aioxmpp.PresenceServer - aioxmpp.PEPClient - aioxmpp.RosterClient - aioxmpp.VersionServer - -Shorthands -########## - -.. function:: make_security_layer - - Alias of :func:`aioxmpp.security_layer.make`. - -""" -from ._version import version_info, __version__, version # NOQA: F401 - -#: The imported :mod:`aioxmpp` version as a tuple. -#: -#: The components of the tuple are, in order: `major version`, `minor version`, -#: `patch level`, and `pre-release identifier`. -#: -#: .. seealso:: -#: -#: :ref:`api-stability` -version_info = version_info - -#: The imported :mod:`aioxmpp` version as a string. -#: -#: The version number is dot-separated; in pre-release or development versions, -#: the version number is followed by a hypen-separated pre-release identifier. -#: -#: .. seealso:: -#: -#: :ref:`api-stability` -__version__ = __version__ - -# XXX: ^ this is a hack to make Sphinx find the docs. We could also be using -# .. data instead of .. autodata, but that has the downside that the actual -# version number isn’t printed in the docs (without additional maintenance -# cost). - -import asyncio # NOQA -# Adds fallback if asyncio version does not provide an ensure_future function. -if not hasattr(asyncio, "ensure_future"): - asyncio.ensure_future = getattr(asyncio, "async") - -from .errors import ( # NOQA - XMPPAuthError, - XMPPCancelError, - XMPPContinueError, - XMPPModifyError, - XMPPWaitError, - ErrorCondition, -) -from .stanza import Presence, IQ, Message # NOQA: F401 -from .structs import ( # NOQA: F401 - JID, - PresenceShow, - PresenceState, - MessageType, - PresenceType, - IQType, - ErrorType, - jid_escape, - jid_unescape, -) -from .security_layer import make as make_security_layer # NOQA: F401 -from .node import Client, PresenceManagedClient # NOQA: F401 - -# services -from .presence import PresenceClient, PresenceServer # NOQA: F401 -from .roster import RosterClient # NOQA: F401 -from .disco import DiscoServer, DiscoClient # NOQA: F401 -from .entitycaps import EntityCapsService # NOQA: F401 -from .muc import MUCClient # NOQA: F401 -from .pubsub import PubSubClient # NOQA: F401 -from .shim import SHIMService # NOQA: F401 -from .adhoc import AdHocClient, AdHocServer # NOQA: F401 -from .avatar import AvatarService # NOQA: F401 -from .blocking import BlockingClient # NOQA: F401 -from .carbons import CarbonsClient # NOQA: F401 -from .ping import PingService # NOQA: F401 -from .pep import PEPClient # NOQA: F401 -from .bookmarks import BookmarkClient # NOQA: F401 -from .version import VersionServer # NOQA: F401 -from .mdr import DeliveryReceiptsService # NOQA: F401 - -from . import httpupload # NOQA: F401 - - -def set_strict_mode(): - from .stanza import Error - from .stream import StanzaStream - from . import structs - Message.type_.type_.allow_coerce = False - IQ.type_.type_.allow_coerce = False - Error.type_.type_.allow_coerce = False - Presence.type_.type_.allow_coerce = False - StanzaStream._ALLOW_ENUM_COERCION = False - structs._USE_COMPAT_ENUM = False diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 2cd726b..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/_version.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/_version.cpython-311.pyc deleted file mode 100644 index 537b418..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/_version.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/cache.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/cache.cpython-311.pyc deleted file mode 100644 index 379f608..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/cache.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/callbacks.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/callbacks.cpython-311.pyc deleted file mode 100644 index 219d4f5..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/callbacks.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/connector.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/connector.cpython-311.pyc deleted file mode 100644 index 1e22971..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/connector.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/custom_queue.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/custom_queue.cpython-311.pyc deleted file mode 100644 index 997d9fe..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/custom_queue.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/dispatcher.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/dispatcher.cpython-311.pyc deleted file mode 100644 index ad98cba..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/dispatcher.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/errors.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/errors.cpython-311.pyc deleted file mode 100644 index 8eee6ae..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/errors.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/hashes.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/hashes.cpython-311.pyc deleted file mode 100644 index 7b05122..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/hashes.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/i18n.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/i18n.cpython-311.pyc deleted file mode 100644 index 5689ca5..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/i18n.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/network.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/network.cpython-311.pyc deleted file mode 100644 index 17e4589..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/network.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/node.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/node.cpython-311.pyc deleted file mode 100644 index 097259b..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/node.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/nonza.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/nonza.cpython-311.pyc deleted file mode 100644 index ae58f05..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/nonza.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/protocol.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/protocol.cpython-311.pyc deleted file mode 100644 index a200cdd..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/protocol.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/rfc3921.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/rfc3921.cpython-311.pyc deleted file mode 100644 index 0c8c7a4..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/rfc3921.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/rfc6120.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/rfc6120.cpython-311.pyc deleted file mode 100644 index 8afbad7..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/rfc6120.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/sasl.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/sasl.cpython-311.pyc deleted file mode 100644 index 0c3efeb..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/sasl.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/security_layer.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/security_layer.cpython-311.pyc deleted file mode 100644 index fbb4803..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/security_layer.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/service.cpython-311.pyc deleted file mode 100644 index 633a7a0..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/ssl_transport.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/ssl_transport.cpython-311.pyc deleted file mode 100644 index fa6489e..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/ssl_transport.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stanza.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stanza.cpython-311.pyc deleted file mode 100644 index eb642d1..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stanza.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/statemachine.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/statemachine.cpython-311.pyc deleted file mode 100644 index 767d088..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/statemachine.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stream.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stream.cpython-311.pyc deleted file mode 100644 index 9899602..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stream.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stringprep.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stringprep.cpython-311.pyc deleted file mode 100644 index fe0f8e1..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/stringprep.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/structs.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/structs.cpython-311.pyc deleted file mode 100644 index 06d3d02..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/structs.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/tasks.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/tasks.cpython-311.pyc deleted file mode 100644 index bb994bd..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/tasks.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/testutils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/testutils.cpython-311.pyc deleted file mode 100644 index a63c837..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/testutils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/tracking.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/tracking.cpython-311.pyc deleted file mode 100644 index d95fe6f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/tracking.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/utils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/utils.cpython-311.pyc deleted file mode 100644 index 40a71ae..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/utils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/xml.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/xml.cpython-311.pyc deleted file mode 100644 index 3b3b617..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/xml.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/xmltestutils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/xmltestutils.cpython-311.pyc deleted file mode 100644 index 591572a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/__pycache__/xmltestutils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/_version.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/_version.py deleted file mode 100644 index ae19cba..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/_version.py +++ /dev/null @@ -1,28 +0,0 @@ -######################################################################## -# File name: _version.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## - -version_info = (0, 13, 3, None) - -__version__ = ".".join(map(str, version_info[:3])) + ("-"+version_info[3] if - version_info[3] else "") - -version = __version__ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__init__.py deleted file mode 100644 index 3ee52ba..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.adhoc` --- Ad-Hoc Commands support (:xep:`50`) -############################################################# - -This subpackage implements support for Ad-Hoc Commands as specified in -:xep:`50`. Both the client and the server side of Ad-Hoc Commands are -supported. - -.. versionadded:: 0.8 - -Client-side -=========== - -.. currentmodule:: aioxmpp - -.. autoclass:: AdHocClient - -.. currentmodule:: aioxmpp.adhoc.service - -.. autoclass:: ClientSession - -Server-side -=========== - -.. currentmodule:: aioxmpp.adhoc - -.. autoclass:: AdHocServer - -.. currentmodule:: aioxmpp.adhoc.service - -.. - .. autoclass:: ServerSession - -XSOs -==== - -.. currentmodule:: aioxmpp.adhoc.xso - -.. autoclass:: Command - -.. autoclass:: Actions - -.. autoclass:: Note - -.. currentmodule:: aioxmpp.adhoc - -Enumerations ------------- - -.. autoclass:: CommandStatus - -.. autoclass:: ActionType -""" - -from .service import ( # NOQA: F401 - AdHocClient, - ClientSession, - AdHocServer, -) - -from .xso import ( # NOQA: F401 - CommandStatus, - ActionType, -) - -from . import xso # NOQA: F401 diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 638d965..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/service.cpython-311.pyc deleted file mode 100644 index a4aee4f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index b754bd9..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/service.py deleted file mode 100644 index 80b7c29..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/service.py +++ /dev/null @@ -1,601 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -# import base64 -import collections -import logging -import random - -# from datetime import timedelta - -import aioxmpp.disco -import aioxmpp.errors -import aioxmpp.disco.xso as disco_xso -import aioxmpp.service -import aioxmpp.structs - -from aioxmpp.utils import namespaces - -from . import xso as adhoc_xso - - -_logger = logging.getLogger(__name__) -_rng = random.SystemRandom() - - -class SessionError(RuntimeError): - pass - - -class ClientCancelledError(SessionError): - pass - - -class AdHocClient(aioxmpp.service.Service): - """ - Access other entities :xep:`50` Ad-Hoc commands. - - This service provides helpers to conveniently access and execute :xep:`50` - Ad-Hoc commands. - - .. automethod:: supports_commands - - .. automethod:: get_commands - - .. automethod:: get_command_info - - .. automethod:: execute - """ - - ORDER_AFTER = [aioxmpp.disco.DiscoClient] - - async def get_commands(self, peer_jid): - """ - Return the list of commands offered by the peer. - - :param peer_jid: JID of the peer to query - :type peer_jid: :class:`~aioxmpp.JID` - :rtype: :class:`list` of :class:`~.disco.xso.Item` - :return: List of command items - - In the returned list, each :class:`~.disco.xso.Item` represents one - command supported by the peer. The :attr:`~.disco.xso.Item.node` - attribute is the identifier of the command which can be used with - :meth:`get_command_info` and :meth:`execute`. - """ - - disco = self.dependencies[aioxmpp.disco.DiscoClient] - response = await disco.query_items( - peer_jid, - node=namespaces.xep0050_commands, - ) - return response.items - - async def get_command_info(self, peer_jid, command_name): - """ - Obtain information about a command. - - :param peer_jid: JID of the peer to query - :type peer_jid: :class:`~aioxmpp.JID` - :param command_name: Node name of the command - :type command_name: :class:`str` - :rtype: :class:`~.disco.xso.InfoQuery` - :return: Service discovery information about the command - - Sends a service discovery query to the service discovery node of the - command. The returned object contains information about the command, - such as the namespaces used by its implementation (generally the - :xep:`4` data forms namespace) and possibly localisations of the - commands name. - - The `command_name` can be obtained by inspecting the listing from - :meth:`get_commands` or from well-known command names as defined for - example in :xep:`133`. - """ - - disco = self.dependencies[aioxmpp.disco.DiscoClient] - response = await disco.query_info( - peer_jid, - node=command_name, - ) - return response - - async def supports_commands(self, peer_jid): - """ - Detect whether a peer supports :xep:`50` Ad-Hoc commands. - - :param peer_jid: JID of the peer to query - :type peer_jid: :class:`aioxmpp.JID` - :rtype: :class:`bool` - :return: True if the peer supports the Ad-Hoc commands protocol, false - otherwise. - - Note that the fact that a peer supports the protocol does not imply - that it offers any commands. - """ - - disco = self.dependencies[aioxmpp.disco.DiscoClient] - response = await disco.query_info( - peer_jid, - ) - - return namespaces.xep0050_commands in response.features - - async def execute(self, peer_jid, command_name): - """ - Start execution of a command with a peer. - - :param peer_jid: JID of the peer to start the command at. - :type peer_jid: :class:`~aioxmpp.JID` - :param command_name: Node name of the command to execute. - :type command_name: :class:`str` - :rtype: :class:`~.adhoc.service.ClientSession` - :return: A started command execution session. - - Initialises a client session and starts execution of the command. The - session is returned. - - This may raise any exception which may be raised by - :meth:`~.adhoc.service.ClientSession.start`. - """ - - session = ClientSession( - self.client.stream, - peer_jid, - command_name, - ) - await session.start() - return session - - -CommandEntry = collections.namedtuple( - "CommandEntry", - [ - "name", - "is_allowed", - "handler", - "features", - ] -) - - -class CommandEntry(aioxmpp.disco.StaticNode): - def __init__(self, name, handler, features=set(), is_allowed=None): - super().__init__() - if isinstance(name, str): - self.__name = aioxmpp.structs.LanguageMap({None: name}) - else: - self.__name = aioxmpp.structs.LanguageMap(name) - self.__handler = handler - - features = set(features) | {namespaces.xep0050_commands} - for feature in features: - self.register_feature(feature) - - self.__is_allowed = is_allowed - - self.register_identity( - "automation", - "command-node", - names=self.__name - ) - - @property - def name(self): - return self.__name - - @property - def handler(self): - return self.__handler - - @property - def is_allowed(self): - return self.__is_allowed - - def is_allowed_for(self, *args, **kwargs): - if self.__is_allowed is None: - return True - return self.__is_allowed(*args, **kwargs) - - def iter_identities(self, stanza): - if not self.is_allowed_for(stanza.from_): - return iter([]) - return super().iter_identities(stanza) - - -class AdHocServer(aioxmpp.service.Service, aioxmpp.disco.Node): - """ - Support for serving Ad-Hoc commands. - - .. .. automethod:: register_stateful_command - - .. automethod:: register_stateless_command - - .. automethod:: unregister_command - """ - - ORDER_AFTER = [aioxmpp.disco.DiscoServer] - - disco_node = aioxmpp.disco.mount_as_node( - "http://jabber.org/protocol/commands" - ) - disco_feature = aioxmpp.disco.register_feature( - "http://jabber.org/protocol/commands" - ) - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - self.register_identity( - "automation", "command-list", - ) - - self._commands = {} - self._disco = self.dependencies[aioxmpp.disco.DiscoServer] - - @aioxmpp.service.iq_handler(aioxmpp.IQType.SET, - adhoc_xso.Command) - async def _handle_command(self, stanza): - try: - info = self._commands[stanza.payload.node] - except KeyError: - raise aioxmpp.errors.XMPPCancelError( - aioxmpp.errors.ErrorCondition.ITEM_NOT_FOUND, - text="no such command: {!r}".format( - stanza.payload.node - ) - ) - - if not info.is_allowed_for(stanza.from_): - raise aioxmpp.errors.XMPPCancelError( - aioxmpp.errors.ErrorCondition.FORBIDDEN, - ) - - return await info.handler(stanza) - - def iter_items(self, stanza): - local_jid = self.client.local_jid - languages = [ - aioxmpp.structs.LanguageRange.fromstr("en"), - ] - - if stanza.lang is not None: - languages.insert(0, aioxmpp.structs.LanguageRange.fromstr( - str(stanza.lang) - )) - - for node, info in self._commands.items(): - if not info.is_allowed_for(stanza.from_): - continue - yield disco_xso.Item( - local_jid, - name=info.name.lookup(languages), - node=node, - ) - - def register_stateless_command(self, node, name, handler, *, - is_allowed=None, - features={namespaces.xep0004_data}): - """ - Register a handler for a stateless command. - - :param node: Name of the command (``node`` in the service discovery - list). - :type node: :class:`str` - :param name: Human-readable name of the command - :type name: :class:`str` or :class:`~.LanguageMap` - :param handler: Coroutine function to run to get the response for a - request. - :param is_allowed: A predicate which determines whether the command is - shown and allowed for a given peer. - :type is_allowed: function or :data:`None` - :param features: Set of features to announce for the command - :type features: :class:`set` of :class:`str` - - When a request for the command is received, `handler` is invoked. The - semantics of `handler` are the same as for - :meth:`~.StanzaStream.register_iq_request_handler`. It must produce a - valid :class:`~.adhoc.xso.Command` response payload. - - If `is_allowed` is not :data:`None`, it is invoked whenever a command - listing is generated and whenever a command request is received. The - :class:`aioxmpp.JID` of the requester is passed as positional argument - to `is_allowed`. If `is_allowed` returns false, the command is not - included in the list and attempts to execute it are rejected with - ```` without calling `handler`. - - If `is_allowed` is :data:`None`, the command is always visible and - allowed. - - The `features` are returned on a service discovery info request for the - command node. By default, the :xep:`4` (Data Forms) namespace is - included, but this can be overridden by passing a different set without - that feature to `features`. - """ - - info = CommandEntry( - name, - handler, - is_allowed=is_allowed, - features=features, - ) - self._commands[node] = info - self._disco.mount_node( - node, - info, - ) - - def unregister_command(self, node): - """ - Unregister a command previously registered. - - :param node: Name of the command (``node`` in the service discovery - list). - :type node: :class:`str` - """ - - -class ClientSession: - """ - Represent an Ad-Hoc command session on the client side. - - :param stream: The stanza stream over which the session is established. - :type stream: :class:`~.StanzaStream` - :param peer_jid: The full JID of the peer to communicate with - :type peer_jid: :class:`~aioxmpp.JID` - :param command_name: The command to run - :type command_name: :class:`str` - - The constructor does not send any stanza, it merely prepares the internal - state. To start the command itself, use the :class:`ClientSession` object - as context manager or call :meth:`start`. - - .. note:: - - The client session returned by :meth:`.AdHocClient.execute` is already - started. - - The `command_name` must be one of the :attr:`~.disco.xso.Item.node` values - as returned by :meth:`.AdHocClient.get_commands`. - - .. automethod:: start - - .. automethod:: proceed - - .. automethod:: close - - The following attributes change depending on the stage of execution of the - command: - - .. autoattribute:: allowed_actions - - .. autoattribute:: first_payload - - .. autoattribute:: response - - .. autoattribute:: status - """ - - def __init__(self, stream, peer_jid, command_name, *, logger=None): - super().__init__() - self._stream = stream - self._peer_jid = peer_jid - self._command_name = command_name - self._logger = logger or _logger - - self._status = None - self._response = None - - @property - def status(self): - """ - The current status of command execution. This is either :data:`None` or - one of the :class:`~.adhoc.CommandStatus` enumeration values. - - Initially, this attribute is :data:`None`. After calls to - :meth:`start`, :meth:`proceed` or :meth:`close`, it takes the value of - the :attr:`~.xso.Command.status` attribute of the response. - """ - - if self._response is not None: - return self._response.status - return None - - @property - def response(self): - """ - The last :class:`~.xso.Command` received from the peer. - - This is initially (and after :meth:`close`) :data:`None`. - """ - - return self._response - - @property - def first_payload(self): - """ - Shorthand to access :attr:`~.xso.Command.first_payload` of the - :attr:`response`. - - This is initially (and after :meth:`close`) :data:`None`. - """ - - if self._response is not None: - return self._response.first_payload - return None - - @property - def sessionid(self): - """ - Shorthand to access :attr:`~.xso.Command.sessionid` of the - :attr:`response`. - - This is initially (and after :meth:`close`) :data:`None`. - """ - - if self._response is not None: - return self._response.sessionid - return None - - @property - def allowed_actions(self): - """ - Shorthand to access :attr:`~.xso.Actions.allowed_actions` of the - :attr:`response`. - - If no response has been received yet or if the response specifies no - set of valid actions, this is the minimal set of allowed actions ( - :attr:`~.ActionType.EXECUTE` and :attr:`~.ActionType.CANCEL`). - """ - - if self._response is not None and self._response.actions is not None: - return self._response.actions.allowed_actions - return {adhoc_xso.ActionType.EXECUTE, - adhoc_xso.ActionType.CANCEL} - - async def start(self): - """ - Initiate the session by starting to execute the command with the peer. - - :return: The :attr:`~.xso.Command.first_payload` of the response - - This sends an empty command IQ request with the - :attr:`~.ActionType.EXECUTE` action. - - The :attr:`status`, :attr:`response` and related attributes get updated - with the newly received values. - """ - - if self._response is not None: - raise RuntimeError("command execution already started") - - request = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - to=self._peer_jid, - payload=adhoc_xso.Command(self._command_name), - ) - - self._response = await self._stream.send_iq_and_wait_for_reply( - request, - ) - - return self._response.first_payload - - async def proceed(self, *, - action=adhoc_xso.ActionType.EXECUTE, - payload=None): - """ - Proceed command execution to the next stage. - - :param action: Action type for proceeding - :type action: :class:`~.ActionTyp` - :param payload: Payload for the request, or :data:`None` - :return: The :attr:`~.xso.Command.first_payload` of the response - - `action` must be one of the actions returned by - :attr:`allowed_actions`. It defaults to :attr:`~.ActionType.EXECUTE`, - which is (alongside with :attr:`~.ActionType.CANCEL`) always allowed. - - `payload` may be a sequence of XSOs, a single XSO or :data:`None`. If - it is :data:`None`, the XSOs from the request are re-used. This is - useful if you modify the payload in-place (e.g. via - :attr:`first_payload`). Otherwise, the payload on the request is set to - the `payload` argument; if it is a single XSO, it is wrapped in a - sequence. - - The :attr:`status`, :attr:`response` and related attributes get updated - with the newly received values. - """ - - if self._response is None: - raise RuntimeError("command execution not started yet") - - if action not in self.allowed_actions: - raise ValueError("action {} not allowed in this stage".format( - action - )) - - cmd = adhoc_xso.Command( - self._command_name, - action=action, - payload=self._response.payload if payload is None else payload, - sessionid=self.sessionid, - ) - - request = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - to=self._peer_jid, - payload=cmd, - ) - - try: - self._response = await self._stream.send_iq_and_wait_for_reply( - request, - ) - except (aioxmpp.errors.XMPPModifyError, - aioxmpp.errors.XMPPCancelError) as exc: - if isinstance(exc.application_defined_condition, - (adhoc_xso.BadSessionID, - adhoc_xso.SessionExpired)): - await self.close() - raise SessionError(exc.text) - if isinstance(exc, aioxmpp.errors.XMPPCancelError): - await self.close() - raise - - return self._response.first_payload - - async def close(self): - if self._response is None: - return - - if self.status != adhoc_xso.CommandStatus.COMPLETED: - request = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - to=self._peer_jid, - payload=adhoc_xso.Command( - self._command_name, - sessionid=self.sessionid, - action=adhoc_xso.ActionType.CANCEL, - ) - ) - - try: - await self._stream.send_iq_and_wait_for_reply( - request, - ) - except aioxmpp.errors.StanzaError as exc: - # we are cancelling only out of courtesy. - # if something goes wrong here, it’s barely worth logging - self._logger.debug( - "ignored stanza error during close(): %r", - exc, - ) - - self._response = None - - async def __aenter__(self): - if self._response is None: - await self.start() - return self - - async def __aexit__(self, exc_type, exc_value, exc_traceback): - await self.close() diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/xso.py deleted file mode 100644 index 6123d01..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/adhoc/xso.py +++ /dev/null @@ -1,228 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import collections.abc -import enum - -import aioxmpp.stanza -import aioxmpp.forms -import aioxmpp.xso as xso - -from aioxmpp.utils import namespaces - -namespaces.xep0050_commands = "http://jabber.org/protocol/commands" - - -class NoteType(enum.Enum): - INFO = "info" - WARN = "warn" - ERROR = "error" - - -class ActionType(enum.Enum): - NEXT = "next" - EXECUTE = "execute" - PREV = "prev" - CANCEL = "cancel" - COMPLETE = "complete" - - -class CommandStatus(enum.Enum): - """ - Describes the status a command execution is in. - - .. attribute:: EXECUTING - - The command is being executed. - - .. attribute:: COMPLETED - - The command has been completed. - - .. attribute:: CANCELED - - The command has been canceled. - """ - - EXECUTING = "executing" - COMPLETED = "completed" - CANCELED = "canceled" - - -class Note(xso.XSO): - TAG = (namespaces.xep0050_commands, "note") - - body = xso.Text( - default=None, - ) - - type_ = xso.Attr( - "type", - type_=xso.EnumCDataType( - NoteType, - ), - default=NoteType.INFO, - ) - - def __init__(self, type_, body): - super().__init__() - self.type_ = type_ - self.body = body - - -class Actions(xso.XSO): - TAG = (namespaces.xep0050_commands, "actions") - - next_is_allowed = xso.ChildFlag( - (namespaces.xep0050_commands, "next"), - ) - - prev_is_allowed = xso.ChildFlag( - (namespaces.xep0050_commands, "prev"), - ) - - complete_is_allowed = xso.ChildFlag( - (namespaces.xep0050_commands, "complete"), - ) - - execute = xso.Attr( - "execute", - type_=xso.EnumCDataType(ActionType), - validator=xso.RestrictToSet({ - ActionType.NEXT, - ActionType.PREV, - ActionType.COMPLETE, - }), - default=None, - ) - - @property - def allowed_actions(self): - result = [ActionType.EXECUTE, ActionType.CANCEL] - if self.prev_is_allowed: - result.append(ActionType.PREV) - if self.next_is_allowed: - result.append(ActionType.NEXT) - if self.complete_is_allowed: - result.append(ActionType.COMPLETE) - return frozenset(result) - - @allowed_actions.setter - def allowed_actions(self, values): - values = frozenset(values) - if ActionType.EXECUTE not in values: - raise ValueError("EXECUTE must always be allowed") - if ActionType.CANCEL not in values: - raise ValueError("CANCEL must always be allowed") - self.prev_is_allowed = ActionType.PREV in values - self.next_is_allowed = ActionType.NEXT in values - self.complete_is_allowed = ActionType.COMPLETE in values - - -@aioxmpp.IQ.as_payload_class -class Command(xso.XSO): - TAG = (namespaces.xep0050_commands, "command") - - actions = xso.Child([Actions]) - - notes = xso.ChildList([Note]) - - action = xso.Attr( - "action", - type_=xso.EnumCDataType(ActionType), - default=ActionType.EXECUTE, - ) - - status = xso.Attr( - "status", - type_=xso.EnumCDataType(CommandStatus), - default=None, - ) - - sessionid = xso.Attr( - "sessionid", - default=None, - ) - - node = xso.Attr( - "node", - ) - - payload = xso.ChildList([ - aioxmpp.forms.Data, - ]) - - def __init__(self, node, *, - action=ActionType.EXECUTE, - status=None, - sessionid=None, - payload=[], - notes=[], - actions=None): - super().__init__() - self.node = node - self.action = action - self.status = status - self.sessionid = sessionid - if not isinstance(payload, collections.abc.Iterable): - self.payload[:] = [payload] - else: - self.payload[:] = payload - self.notes[:] = notes - self.actions = actions - - @property - def first_payload(self): - try: - return self.payload[0] - except IndexError: - return - - -MalformedAction = aioxmpp.stanza.make_application_error( - "MalformedAction", - (namespaces.xep0050_commands, "malformed-action"), -) - -BadAction = aioxmpp.stanza.make_application_error( - "BadAction", - (namespaces.xep0050_commands, "bad-action"), -) - -BadLocale = aioxmpp.stanza.make_application_error( - "BadLocale", - (namespaces.xep0050_commands, "bad-locale"), -) - -BadPayload = aioxmpp.stanza.make_application_error( - "BadPayload", - (namespaces.xep0050_commands, "bad-payload"), -) - -BadSessionID = aioxmpp.stanza.make_application_error( - "BadSessionID", - (namespaces.xep0050_commands, "bad-sessionid"), -) - -SessionExpired = aioxmpp.stanza.make_application_error( - "SessionExpired", - (namespaces.xep0050_commands, "session-expired"), -) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__init__.py deleted file mode 100644 index c5f5f67..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.avatar` --- User avatar support (:xep:`0084`) -############################################################ - -This module provides support for publishing and retrieving user -avatars as per :xep:`User Avatar <84>`. - -Services -======== - -The following service is provided by this subpackage: - -.. currentmodule:: aioxmpp - -.. autosummary:: - - AvatarService - -The detailed documentation of the classes follows: - -.. autoclass:: AvatarService() - -.. currentmodule:: aioxmpp.avatar - -Data Representation -=================== - -The following class is used to describe the possible locations of an -avatar image: - -.. autoclass:: AvatarSet - -.. module:: aioxmpp.avatar.service -.. currentmodule:: aioxmpp.avatar.service -.. autoclass:: AbstractAvatarDescriptor() - -.. currentmodule:: aioxmpp.avatar - -Helpers -======= - -.. autofunction:: normalize_id - -How to work with avatar descriptors -=================================== - -.. currentmodule:: aioxmpp.avatar.service - -One you have retrieved the avatar descriptor list, the correct way to -handle it in the application: - -1. Select the avatar you prefer based on the - :attr:`~AbstractAvatarDescriptor.can_get_image_bytes_via_xmpp`, and - metadata information (:attr:`~AbstractAvatarDescriptor.mime_type`, - :attr:`~AbstractAvatarDescriptor.width`, - :attr:`~AbstractAvatarDescriptor.height`, - :attr:`~AbstractAvatarDescriptor.nbytes`). If you cache avatar - images it might be a good choice to choose an avatar image you - already have cached based on - :attr:`~AbstractAvatarDescriptor.normalized_id`. - -2. If :attr:`~AbstractAvatarDescriptor.can_get_image_bytes_via_xmpp` - is true, try to retrieve the image by - :attr:`~AbstractAvatarDescriptor.get_image_bytes()`; if it is false - try to retrieve the object at the URL - :attr:`~AbstractAvatarDescriptor.url`. -""" - -from .service import (AvatarSet, AvatarService, # NOQA: F401 - normalize_id) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e3a9fff..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/service.cpython-311.pyc deleted file mode 100644 index 05bbcb1..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index b1dd39c..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/service.py deleted file mode 100644 index 8b6be94..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/service.py +++ /dev/null @@ -1,1020 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -import hashlib -import logging -import warnings - -import aioxmpp -import aioxmpp.callbacks as callbacks -import aioxmpp.service as service -import aioxmpp.disco as disco -import aioxmpp.pep as pep -import aioxmpp.presence as presence -import aioxmpp.pubsub as pubsub -import aioxmpp.vcard as vcard - -from aioxmpp.cache import LRUDict -from aioxmpp.utils import namespaces, gather_reraise_multi - -from . import xso as avatar_xso - -logger = logging.getLogger(__name__) - - -def normalize_id(id_): - """ - Normalize a SHA1 sum encoded as hexadecimal number in ASCII. - - This does nothing but lowercase the string as to enable robust - comparison. - """ - return id_.lower() - - -class AvatarSet: - """ - A list of sources of an avatar. - - Exactly one of the sources must include image data in the - ``image/png`` format. The others provide the location of the - image data as an URL. - - Adding pointer avatar data is not yet supported. - - .. automethod:: add_avatar_image - """ - - def __init__(self): - self._image_bytes = None - self._png_id = None - self._metadata = avatar_xso.Metadata() - - @property - def image_bytes(self): - """ - The image data bytes for MIME type ``text/png``. - """ - return self._image_bytes - - @property - def metadata(self): - """ - The :class:`Metadata` XSO corresponding to this avatar set. - """ - return self._metadata - - @property - def png_id(self): - """ - The SHA1 of the ``image/png`` image data. - - This id is always normalized in the sense of :function:`normalize_id`. - """ - return self._png_id - - def add_avatar_image(self, mime_type, *, id_=None, - image_bytes=None, width=None, height=None, - url=None, nbytes=None): - """ - Add a source of the avatar image. - - All sources of an avatar image added to an avatar set must be - *the same image*, in different formats and sizes. - - :param mime_type: The MIME type of the avatar image. - :param id_: The SHA1 of the image data. - :param nbytes: The size of the image data in bytes. - :param image_bytes: The image data, this must be supplied only - in one call. - :param url: The URL of the avatar image. - :param height: The height of the image in pixels (optional). - :param width: The width of the image in pixels (optional). - - `id_` and `nbytes` may be omitted if and only if `image_data` - is given and `mime_type` is ``image/png``. If they are - supplied *and* image data is given, they are checked to match - the image data. - - It is the caller's responsibility to assure that the provided - links exist and the files have the correct SHA1 sums. - """ - - if mime_type == "image/png": - if image_bytes is not None: - if self._image_bytes is not None: - raise RuntimeError( - "Only one avatar image may be published directly." - ) - - sha1 = hashlib.sha1() - sha1.update(image_bytes) - id_computed = normalize_id(sha1.hexdigest()) - if id_ is not None: - id_ = normalize_id(id_) - if id_ != id_computed: - raise RuntimeError( - "The given id does not match the SHA1 of " - "the image data." - ) - else: - id_ = id_computed - - nbytes_computed = len(image_bytes) - if nbytes is not None: - if nbytes != nbytes_computed: - raise RuntimeError( - "The given length does not match the length " - "of the image data." - ) - else: - nbytes = nbytes_computed - - self._image_bytes = image_bytes - self._png_id = id_ - - if image_bytes is None and url is None: - raise RuntimeError( - "Either the image bytes or an url to retrieve the avatar " - "image must be given." - ) - - if nbytes is None: - raise RuntimeError( - "Image data length is not given an not inferable " - "from the other arguments." - ) - - if id_ is None: - raise RuntimeError( - "The SHA1 of the image data is not given an not inferable " - "from the other arguments." - ) - - if image_bytes is not None and mime_type != "image/png": - raise RuntimeError( - "The image bytes can only be given for image/png data." - ) - - self._metadata.info[mime_type].append( - avatar_xso.Info( - id_=id_, mime_type=mime_type, nbytes=nbytes, - width=width, height=height, url=url - ) - ) - - -class AbstractAvatarDescriptor: - """ - Description of the properties of and how to retrieve a specific - avatar. - - The following attributes are available for all instances: - - .. autoattribute:: remote_jid - - .. autoattribute:: id_ - - .. autoattribute:: normalized_id - - .. autoattribute:: can_get_image_bytes_via_xmpp - - .. autoattribute:: has_image_data_in_pubsub - - The following attributes may be :data:`None` and are supposed to - be used as hints for selection of the avatar to download: - - .. autoattribute:: nbytes - - .. autoattribute:: width - - .. autoattribute:: height - - .. autoattribute:: mime_type - - If this attribute is not :data:`None` it is an URL that points to - the location of the avatar image: - - .. autoattribute:: url - - The image data belonging to the descriptor can be retrieved by the - following coroutine: - - .. automethod:: get_image_bytes - """ - - def __init__(self, remote_jid, id_, *, mime_type=None, - nbytes=None, width=None, height=None, url=None): - self._remote_jid = remote_jid - self._mime_type = mime_type - self._id = id_ - self._nbytes = nbytes - self._width = width - self._height = height - self._url = url - - def __eq__(self, other): - return (self._remote_jid == other._remote_jid and - self._mime_type == other._mime_type and - self._id == other._id and - self._nbytes == other._nbytes and - self._width == other._width and - self._height == other._height and - self._url == other._url) - - async def get_image_bytes(self): - """ - Try to retrieve the image data corresponding to this avatar - descriptor. - - :returns: the image contents - :rtype: :class:`bytes` - - :raises NotImplementedError: if we do not implement the - capability to retrieve the image data of this type. It is - guaranteed to not raise :class:`NotImplementedError` if - :attr:`can_get_image_bytes_via_xmpp` is true. - - :raises RuntimeError: if the image data described by this - descriptor is not at the specified location. - - :raises aiomxpp.XMPPCancelError: if trying to retrieve the - image data causes an XMPP error. - """ - raise NotImplementedError - - @property - def can_get_image_bytes_via_xmpp(self): - """ - Return whether :meth:`get_image_bytes` raises - :class:`NotImplementedError`. - """ - return False - - @property - def has_image_data_in_pubsub(self): - """ - Whether the image can be retrieved from PubSub. - - .. deprecated:: 0.10 - - Use :attr:`can_get_image_bytes_via_xmpp` instead. - - As we support vCard based avatars now the name of this is - misleading. - - This attribute will be removed in aioxmpp 1.0 - """ - warnings.warn( - "the has_image_data_in_pubsub attribute is deprecated and will be" - " removed in 1.0", - DeprecationWarning, - stacklevel=1 - ) - return self.can_get_image_bytes_via_xmpp - - @property - def remote_jid(self): - """ - The remote JID this avatar belongs to. - """ - return self._remote_jid - - @property - def url(self): - """ - The URL where the avatar image data can be found. - - This may be :data:`None` if the avatar is not given as an URL - of the image data. - """ - return self._url - - @property - def width(self): - """ - The width of the avatar image in pixels. - - This is :data:`None` if this information is not supplied. - """ - return self._width - - @property - def height(self): - """ - The height of the avatar image in pixels. - - This is :data:`None` if this information is not supplied. - """ - return self._height - - @property - def nbytes(self): - """ - The size of the avatar image data in bytes. - """ - return self._nbytes - - @property - def id_(self): - """ - The SHA1 of the image encoded as hexadecimal number in ASCII. - - This is the original value returned from the underlying - protocol and should be used for any further interaction with - the underlying protocol. - """ - return self._id - - @property - def normalized_id(self): - """ - The normalized SHA1 of the image data. - - This is supposed to be used for caching and comparison. - """ - return normalize_id(self._id) - - @property - def mime_type(self): - """ - The MIME type of the image data. - """ - return self._mime_type - - -class PubsubAvatarDescriptor(AbstractAvatarDescriptor): - - def __init__(self, remote_jid, id_, *, pubsub=None, **kwargs): - super().__init__(remote_jid, id_, **kwargs) - self._pubsub = pubsub - - def __eq__(self, other): - return (isinstance(other, PubsubAvatarDescriptor) and - super().__eq__(other)) - - @property - def can_get_image_bytes_via_xmpp(self): - return True - - async def get_image_bytes(self): - image_data = await self._pubsub.get_items_by_id( - self._remote_jid, - namespaces.xep0084_data, - [self.id_], - ) - if not image_data.payload.items: - raise RuntimeError("Avatar image data is not set.") - - item, = image_data.payload.items - return item.registered_payload.data - - -class HttpAvatarDescriptor(AbstractAvatarDescriptor): - - async def get_image_bytes(self): - raise NotImplementedError - - def __eq__(self, other): - return (isinstance(other, HttpAvatarDescriptor) and - super().__eq__(other)) - - -class VCardAvatarDescriptor(AbstractAvatarDescriptor): - - def __init__(self, remote_jid, id_, *, vcard=None, image_bytes=None, - **kwargs): - super().__init__(remote_jid, id_, **kwargs) - self._vcard = vcard - self._image_bytes = image_bytes - - def __eq__(self, other): - # NOTE: we explicitly do *not* check for the equality of - # image bytes: image bytes is a hidden optimization - return (isinstance(other, VCardAvatarDescriptor) and - super().__eq__(other)) - - @property - def can_get_image_bytes_via_xmpp(self): - return True - - async def get_image_bytes(self): - if self._image_bytes is not None: - return self._image_bytes - - logger.debug("retrieving vCard %s", self._remote_jid) - vcard = await self._vcard.get_vcard(self._remote_jid) - photo = vcard.get_photo_data() - if photo is None: - raise RuntimeError("Avatar image is not set") - - logger.debug("returning vCard avatar %s", self._remote_jid) - return photo - - -class AvatarService(service.Service): - """ - Access and publish User Avatars (:xep:`84`). Fallback to vCard - based avatars (:xep:`153`) if no PEP avatar is available. - - This service provides an interface for accessing the avatar of other - entities in the network, getting notifications on avatar changes and - publishing an avatar for this entity. - - .. versionchanged:: 0.10 - - Support for :xep:`vCard-Based Avatars <153>` was added. - - Observing avatars: - - .. note:: :class:`AvatarService` only caches the metadata, not the - actual image data. This is the job of the caller. - - .. signal:: on_metadata_changed(jid, metadata) - - Fires when avatar metadata changes. - - :param jid: The JID which the avatar belongs to. - :param metadata: The new metadata descriptors. - :type metadata: a sequence of - :class:`~aioxmpp.avatar.service.AbstractAvatarDescriptor` - instances - - .. automethod:: get_avatar_metadata - - .. automethod:: subscribe - - Publishing avatars: - - .. automethod:: publish_avatar_set - - .. automethod:: disable_avatar - - .. automethod:: wipe_avatar - - Configuration: - - .. autoattribute:: synchronize_vcard - - .. autoattribute:: advertise_vcard - - .. attribute:: avatar_pep - - The PEP descriptor for claiming the avatar metadata namespace. - The value is a :class:`~aioxmpp.pep.service.RegisteredPEPNode`, - whose :attr:`~aioxmpp.pep.service.RegisteredPEPNode.notify` - property can be used to disable or enable the notification - feature. - - .. autoattribute:: metadata_cache_size - :annotation: = 200 - """ - - ORDER_AFTER = [ - disco.DiscoClient, - disco.DiscoServer, - pubsub.PubSubClient, - pep.PEPClient, - vcard.VCardService, - presence.PresenceClient, - presence.PresenceServer, - ] - - avatar_pep = pep.register_pep_node( - namespaces.xep0084_metadata, - notify=True, - ) - - on_metadata_changed = callbacks.Signal() - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - self._has_pep_avatar = set() - self._metadata_cache = LRUDict() - self._metadata_cache.maxsize = 200 - self._pubsub = self.dependencies[pubsub.PubSubClient] - self._pep = self.dependencies[pep.PEPClient] - self._presence_server = self.dependencies[presence.PresenceServer] - self._disco = self.dependencies[disco.DiscoClient] - self._vcard = self.dependencies[vcard.VCardService] - # we use this lock to prevent race conditions between different - # calls of the methods by one client. - # XXX: Other, independent clients may still cause inconsistent - # data by race conditions, this should be fixed by at least - # checking for consistent data after an update. - self._publish_lock = asyncio.Lock() - self._synchronize_vcard = False - self._advertise_vcard = True - self._vcard_resource_interference = set() - self._vcard_id = None - self._vcard_rehashing_for = None - self._vcard_rehash_task = None - - @property - def metadata_cache_size(self): - """ - Maximum number of cache entries in the avatar metadata cache. - - This is mostly a measure to prevent malicious peers from - exhausting memory by spamming vCard based avatar metadata for - different resources. - - .. versionadded:: 0.10 - - """ - return self._metadata_cache.maxsize - - @metadata_cache_size.setter - def metadata_cache_size(self, value): - self._metadata_cache.maxsize = value - - @property - def synchronize_vcard(self): - """ - Set this property to true to enable publishing the a vCard avatar. - - This property defaults to false. For the setting true to have - effect, you have to publish your avatar with :meth:`publish_avatar_set` - or :meth:`disable_avatar` *after* this switch has been set to true. - """ - return self._synchronize_vcard - - @synchronize_vcard.setter - def synchronize_vcard(self, value): - self._synchronize_vcard = bool(value) - - @property - def advertise_vcard(self): - """ - Set this property to false to disable advertisement of the vCard - avatar via presence broadcast. - - Note, that this reduces traffic, since it makes the presence - stanzas smaller and we no longer have to recalculate the hash, - this also disables vCard advertisement for all other - resources of the bare local jid, by the business rules of - :xep:`0153`. - - Note that, when enabling this feature again the vCard has to - be fetched from the server to recalculate the hash. - """ - return self._advertise_vcard - - @advertise_vcard.setter - def advertise_vcard(self, value): - self._advertise_vcard = bool(value) - if self._advertise_vcard: - self._vcard_id = None - self._start_rehash_task() - - @service.depfilter(aioxmpp.stream.StanzaStream, - "service_outbound_presence_filter") - def _attach_vcard_notify_to_presence(self, stanza): - if self._advertise_vcard: - if self._vcard_resource_interference: - # do not advertise the hash if there is resource interference - stanza.xep0153_x = avatar_xso.VCardTempUpdate() - else: - stanza.xep0153_x = avatar_xso.VCardTempUpdate(self._vcard_id) - - return stanza - - def _update_metadata(self, cache_jid, metadata): - try: - cached_metadata = self._metadata_cache[cache_jid] - except KeyError: - pass - else: - if cached_metadata == metadata: - return - - self._metadata_cache[cache_jid] = metadata - self.on_metadata_changed( - cache_jid, - metadata - ) - - def _handle_notify(self, full_jid, stanza): - # handle resource interference as per XEP-153 business rules, - # we go along with this tracking even if vcard advertisement - # is off - if (full_jid.bare() == self.client.local_jid.bare() and - full_jid != self.client.local_jid): - if stanza.xep0153_x is None: - self._vcard_resource_interference.add(full_jid) - else: - if self._vcard_resource_interference: - self._vcard_resource_interference.discard(full_jid) - if not self._vcard_resource_interference: - self._vcard_id = None - - # otherwise ignore stanzas without xep0153_x payload, or - # no photo tag. - if stanza.xep0153_x is None: - return - - if stanza.xep0153_x.photo is None: - return - - # special case MUC presence – otherwise the vcard is retrieved - # for the bare jid - if stanza.xep0045_muc_user is not None: - cache_jid = full_jid - else: - cache_jid = full_jid.bare() - - if cache_jid not in self._has_pep_avatar: - metadata = self._cook_vcard_notify(cache_jid, stanza) - self._update_metadata(cache_jid, metadata) - - # trigger the download of the vCard and calculation of the - # vCard avatar hash, if some other resource of our bare jid - # reported a hash distinct from ours! - # don't do this if there is a non-compliant resource, we don't - # send the hash in that case anyway - if (full_jid.bare() == self.client.local_jid.bare() and - full_jid != self.client.local_jid and - self._advertise_vcard and - not self._vcard_resource_interference): - if (self._vcard_id is None or - stanza.xep0153_x.photo.lower() != - self._vcard_id.lower()): - - # do not rehash if we already have a rehash task that - # was triggered by an update with the same hash - if (self._vcard_rehashing_for is None or - self._vcard_rehashing_for != - stanza.xep0153_x.photo.lower()): - self._vcard_rehashing_for = stanza.xep0153_x.photo.lower() - self._start_rehash_task() - - def _start_rehash_task(self): - if self._vcard_rehash_task is not None: - self._vcard_rehash_task.cancel() - - self._vcard_id = None - # as per XEP immediately resend the presence with empty update - # element, as this is not synchronous it might already contaiin - # the new hash, but this is okay as well (as it makes the cached - # presence stanzas coherent as well). - self._presence_server.resend_presence() - - self._vcard_rehash_task = asyncio.ensure_future( - self._calculate_vcard_id() - ) - - def set_new_vcard_id(fut): - self._vcard_rehashing_for = None - if not fut.cancelled(): - self._vcard_id = fut.result() - - self._vcard_rehash_task.add_done_callback( - set_new_vcard_id - ) - - async def _calculate_vcard_id(self): - self.logger.debug("updating vcard hash") - vcard = await self._vcard.get_vcard() - self.logger.debug("got vcard for hash update: %s", vcard) - photo = vcard.get_photo_data() - - # if no photo is set in the vcard, set an empty element - # in the update; according to the spec this means the avatar - # is disabled - if photo is None: - self.logger.debug("no photo in vcard, advertising as such") - return "" - - sha1 = hashlib.sha1() - sha1.update(photo) - new_hash = sha1.hexdigest().lower() - self.logger.debug("updated hash to %s", new_hash) - return new_hash - - @service.depsignal(presence.PresenceClient, "on_available") - def _handle_on_available(self, full_jid, stanza): - self._handle_notify(full_jid, stanza) - - @service.depsignal(presence.PresenceClient, "on_changed") - def _handle_on_changed(self, full_jid, stanza): - self._handle_notify(full_jid, stanza) - - @service.depsignal(presence.PresenceClient, "on_unavailable") - def _handle_on_unavailable(self, full_jid, stanza): - if full_jid.bare() == self.client.local_jid.bare(): - if self._vcard_resource_interference: - self._vcard_resource_interference.discard(full_jid) - if not self._vcard_resource_interference: - self._start_rehash_task() - - # correctly handle MUC avatars - if stanza.xep0045_muc_user is not None: - self._metadata_cache.pop(full_jid, None) - - def _cook_vcard_notify(self, jid, stanza): - result = [] - # note: an empty photo element correctly - # results in an empty avatar metadata list - if stanza.xep0153_x.photo: - result.append( - VCardAvatarDescriptor( - remote_jid=jid, - id_=stanza.xep0153_x.photo, - mime_type=None, - vcard=self._vcard, - nbytes=None, - ) - ) - return result - - def _cook_metadata(self, jid, items): - def iter_metadata_info_nodes(items): - for item in items: - yield from item.registered_payload.iter_info_nodes() - - result = [] - for info_node in iter_metadata_info_nodes(items): - if info_node.url is not None: - descriptor = HttpAvatarDescriptor( - remote_jid=jid, - id_=info_node.id_, - mime_type=info_node.mime_type, - nbytes=info_node.nbytes, - width=info_node.width, - height=info_node.height, - url=info_node.url, - ) - else: - descriptor = PubsubAvatarDescriptor( - remote_jid=jid, - id_=info_node.id_, - mime_type=info_node.mime_type, - nbytes=info_node.nbytes, - width=info_node.width, - height=info_node.height, - pubsub=self._pubsub, - ) - result.append(descriptor) - - return result - - @service.attrsignal(avatar_pep, "on_item_publish") - def _handle_pubsub_publish(self, jid, node, item, *, message=None): - # update the metadata cache - metadata = self._cook_metadata(jid, [item]) - self._has_pep_avatar.add(jid) - self._update_metadata(jid, metadata) - - async def _get_avatar_metadata_vcard(self, jid): - logger.debug("trying vCard avatar as fallback for %s", jid) - vcard = await self._vcard.get_vcard(jid) - photo = vcard.get_photo_data() - mime_type = vcard.get_photo_mime_type() - if photo is None: - return [] - - logger.debug("success vCard avatar as fallback for %s", - jid) - sha1 = hashlib.sha1() - sha1.update(photo) - return [VCardAvatarDescriptor( - remote_jid=jid, - id_=sha1.hexdigest(), - mime_type=mime_type, - nbytes=len(photo), - vcard=self._vcard, - image_bytes=photo, - )] - - async def _get_avatar_metadata_pep(self, jid): - try: - metadata_raw = await self._pubsub.get_items( - jid, - namespaces.xep0084_metadata, - max_items=1 - ) - except aioxmpp.XMPPCancelError as e: - # transparently map feature-not-implemented and - # item-not-found to be equivalent unset avatar - if e.condition in ( - aioxmpp.ErrorCondition.FEATURE_NOT_IMPLEMENTED, - aioxmpp.ErrorCondition.ITEM_NOT_FOUND): - return [] - raise - - self._has_pep_avatar.add(jid) - return self._cook_metadata(jid, metadata_raw.payload.items) - - async def get_avatar_metadata(self, jid, *, require_fresh=False, - disable_pep=False): - """ - Retrieve a list of avatar descriptors. - - :param jid: the JID for which to retrieve the avatar metadata. - :type jid: :class:`aioxmpp.JID` - :param require_fresh: if true, do not return results from the - avatar metadata cache, but retrieve them again from the server. - :type require_fresh: :class:`bool` - :param disable_pep: if true, do not try to retrieve the avatar - via pep, only try the vCard fallback. This usually only - useful when querying avatars via MUC, where the PEP request - would be invalid (since it would be for a full jid). - :type disable_pep: :class:`bool` - - :returns: an iterable of avatar descriptors. - :rtype: a :class:`list` of - :class:`~aioxmpp.avatar.service.AbstractAvatarDescriptor` - instances - - Returning an empty list means that the avatar not set. - - We mask a :class:`XMPPCancelError` in the case that it is - ``feature-not-implemented`` or ``item-not-found`` and return - an empty list of avatar descriptors, since this is - semantically equivalent to not having an avatar. - - .. note:: - - It is usually an error to get the avatar for a full jid, - normally, the avatar is set for the bare jid of a user. The - exception are vCard avatars over MUC, where the IQ requests - for the vCard may be translated by the MUC server. It is - recommended to use the `disable_pep` option in that case. - """ - - if require_fresh: - self._metadata_cache.pop(jid, None) - else: - try: - return self._metadata_cache[jid] - except KeyError: - pass - - if disable_pep: - metadata = [] - else: - metadata = await self._get_avatar_metadata_pep(jid) - - # try the vcard fallback, note: we don't try this - # if the PEP avatar is disabled! - if not metadata and jid not in self._has_pep_avatar: - metadata = await self._get_avatar_metadata_vcard(jid) - - # if a notify was fired while we waited for the results, then - # use the version in the cache, this will mitigate the race - # condition because if our version is actually newer we will - # soon get another notify for this version change! - if jid not in self._metadata_cache: - self._update_metadata(jid, metadata) - return self._metadata_cache[jid] - - async def subscribe(self, jid): - """ - Explicitly subscribe to metadata change notifications for `jid`. - """ - await self._pubsub.subscribe(jid, namespaces.xep0084_metadata) - - @aioxmpp.service.depsignal(aioxmpp.stream.StanzaStream, - "on_stream_destroyed") - def handle_stream_destroyed(self, reason): - self._metadata_cache.clear() - self._vcard_resource_interference.clear() - self._has_pep_avatar.clear() - - async def publish_avatar_set(self, avatar_set): - """ - Make `avatar_set` the current avatar of the jid associated with this - connection. - - If :attr:`synchronize_vcard` is true and PEP is available the - vCard is only synchronized if the PEP update is successful. - - This means publishing the ``image/png`` avatar data and the - avatar metadata set in pubsub. The `avatar_set` must be an - instance of :class:`AvatarSet`. If :attr:`synchronize_vcard` is - true the avatar is additionally published in the user vCard. - """ - id_ = avatar_set.png_id - - done = False - async with self._publish_lock: - if await self._pep.available(): - await self._pep.publish( - namespaces.xep0084_data, - avatar_xso.Data(avatar_set.image_bytes), - id_=id_ - ) - - await self._pep.publish( - namespaces.xep0084_metadata, - avatar_set.metadata, - id_=id_ - ) - done = True - - if self._synchronize_vcard: - my_vcard = await self._vcard.get_vcard() - my_vcard.set_photo_data("image/png", - avatar_set.image_bytes) - self._vcard_id = avatar_set.png_id - await self._vcard.set_vcard(my_vcard) - self._presence_server.resend_presence() - done = True - - if not done: - raise RuntimeError( - "failed to publish avatar: no protocol available" - ) - - async def _disable_vcard_avatar(self): - my_vcard = await self._vcard.get_vcard() - my_vcard.clear_photo_data() - self._vcard_id = "" - await self._vcard.set_vcard(my_vcard) - self._presence_server.resend_presence() - - async def disable_avatar(self): - """ - Temporarily disable the avatar. - - If :attr:`synchronize_vcard` is true, the vCard avatar is - disabled (even if disabling the PEP avatar fails). - - This is done by setting the avatar metadata node empty and if - :attr:`synchronize_vcard` is true, downloading the vCard, - removing the avatar data and re-uploading the vCard. - - This method does not error if neither protocol is active. - - :raises aioxmpp.errors.GatherError: if an exception is raised - by the spawned tasks. - """ - - async with self._publish_lock: - todo = [] - if self._synchronize_vcard: - todo.append(self._disable_vcard_avatar()) - - if await self._pep.available(): - todo.append(self._pep.publish( - namespaces.xep0084_metadata, - avatar_xso.Metadata() - )) - - await gather_reraise_multi(*todo, message="disable_avatar") - - async def wipe_avatar(self): - """ - Remove all avatar data stored on the server. - - If :attr:`synchronize_vcard` is true, the vCard avatar is - disabled even if disabling the PEP avatar fails. - - This is equivalent to :meth:`disable_avatar` for vCard-based - avatars, but will also remove the data PubSub node for - PEP avatars. - - This method does not error if neither protocol is active. - - :raises aioxmpp.errors.GatherError: if an exception is raised - by the spawned tasks. - """ - - async def _wipe_pep_avatar(): - await self._pep.publish( - namespaces.xep0084_metadata, - avatar_xso.Metadata() - ) - await self._pep.publish( - namespaces.xep0084_data, - avatar_xso.Data(b'') - ) - - async with self._publish_lock: - todo = [] - if self._synchronize_vcard: - todo.append(self._disable_vcard_avatar()) - - if await self._pep.available(): - todo.append(_wipe_pep_avatar()) - - await gather_reraise_multi(*todo, message="wipe_avatar") diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/xso.py deleted file mode 100644 index f327623..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/avatar/xso.py +++ /dev/null @@ -1,211 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import aioxmpp.xso as xso -import aioxmpp.pubsub.xso as pubsub_xso - -from aioxmpp.utils import namespaces - -from ..stanza import Presence - - -namespaces.xep0084_data = "urn:xmpp:avatar:data" -namespaces.xep0084_metadata = "urn:xmpp:avatar:metadata" - -namespaces.xep0153 = "vcard-temp:x:update" - - -class VCardTempUpdate(xso.XSO): - """ - The vcard update notify element as per :xep:`0153` - """ - - TAG = (namespaces.xep0153, "x") - - def __init__(self, photo=None): - self.photo = photo - - photo = xso.ChildText((namespaces.xep0153, "photo"), - type_=xso.String(), - default=None) - - -Presence.xep0153_x = xso.Child([VCardTempUpdate]) - - -@pubsub_xso.as_payload_class -class Data(xso.XSO): - """ - A data node, as used to publish and receive the avatar image data - as image/png. - - .. attribute:: data - - The binary image data. - """ - TAG = (namespaces.xep0084_data, "data") - - data = xso.Text(type_=xso.Base64Binary()) - - def __init__(self, image_data): - self.data = image_data - - -class Info(xso.XSO): - """ - An info node specifying avatar metadata for a specific MIME type. - - .. attribute:: id_ - - The SHA1 of the avatar image data. - - .. attribute:: mime_type - - The MIME type of the avatar image. - - .. attribute:: nbytes - - The size of the image data in bytes. - - .. attribute:: width - - The width of the image in pixels. Defaults to :data:`None`. - - .. attribute:: height - - The height of the image in pixels. Defaults to :data:`None`. - - .. attribute:: url - - The URL of the image. Defaults to :data:`None`. - """ - TAG = (namespaces.xep0084_metadata, "info") - - id_ = xso.Attr(tag="id", type_=xso.String()) - mime_type = xso.Attr(tag="type", type_=xso.String()) - nbytes = xso.Attr(tag="bytes", type_=xso.Integer()) - width = xso.Attr(tag="width", type_=xso.Integer(), default=None) - height = xso.Attr(tag="height", type_=xso.Integer(), default=None) - url = xso.Attr(tag="url", type_=xso.String(), default=None) - - def __init__(self, id_, mime_type, nbytes, width=None, - height=None, url=None): - self.id_ = id_ - self.mime_type = mime_type - self.nbytes = nbytes - self.width = width - self.height = height - self.url = url - - -class Pointer(xso.XSO): - """ - A pointer metadata node. The contents are implementation defined. - - The following attributes may be present (they default to - :data:`None`): - - .. attribute:: id_ - - The SHA1 of the avatar image data. - - .. attribute:: mime_type - - The MIME type of the avatar image. - - .. attribute:: nbytes - - The size of the image data in bytes. - - .. attribute:: width - - The width of the image in pixels. - - .. attribute:: height - - The height of the image in pixels. - """ - TAG = (namespaces.xep0084_metadata, "pointer") - - # according to the XEP those MAY occur if their values are known - id_ = xso.Attr(tag="id", type_=xso.String(), default=None) - mime_type = xso.Attr(tag="type", type_=xso.String(), default=None) - nbytes = xso.Attr(tag="bytes", type_=xso.Integer(), default=None) - width = xso.Attr(tag="width", type_=xso.Integer(), default=None) - height = xso.Attr(tag="height", type_=xso.Integer(), default=None) - - registered_payload = xso.Child([]) - unregistered_payload = xso.Collector() - - @classmethod - def as_payload_class(mycls, cls): - """ - Register the given class `cls` as possible payload for a - :class:`Pointer`. - - Return the class, to allow this to be used as decorator. - """ - - mycls.register_child( - Pointer.registered_payload, - cls - ) - - return cls - - def __init__(self, payload, id_, mime_type, nbytes, width=None, - height=None, url=None): - self.registered_payload = payload - - self.id_ = id_ - self.mime_type = mime_type - self.nbytes = nbytes - self.width = width - self.height = height - - -@pubsub_xso.as_payload_class -class Metadata(xso.XSO): - """ - A metadata node which used to publish and reveice avatar image - metadata. - - .. attribute:: info - - A map from the MIME type to the corresponding :class:`Info` XSO. - - .. attribute:: pointer - - A list of the :class:`Pointer` children. - """ - TAG = (namespaces.xep0084_metadata, "metadata") - - info = xso.ChildMap([Info], key=lambda x: x.mime_type) - pointer = xso.ChildList([Pointer]) - - def iter_info_nodes(self): - """ - Iterate over all :class:`Info` children. - """ - info_map = self.info - for mime_type in info_map: - for metadata_info_node in info_map[mime_type]: - yield metadata_info_node diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__init__.py deleted file mode 100644 index db6cb7a..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__init__.py +++ /dev/null @@ -1,311 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -import collections -import contextlib -import importlib -import math -import functools -import time -import os - -from nose.plugins import Plugin - - -def scaleinfo(n, significant_digits=None): - if abs(n) == 0: - order_of_magnitude = 0 - else: - order_of_magnitude = math.floor(math.log(n, 10)) - - prefix_level = math.floor(order_of_magnitude / 3) - prefix_level = min(6, max(-6, prefix_level)) - - PREFIXES = { - -6: "a", - -5: "f", - -4: "p", - -3: "n", - -2: "μ", - -1: "m", - 0: "", - 1: "k", - 2: "M", - 3: "G", - 4: "T", - 5: "P", - 6: "E", - } - - prefix_magnitude = prefix_level*3 - scale = 10**prefix_magnitude - - n /= scale - - if significant_digits is not None: - digits = order_of_magnitude - prefix_magnitude + 1 - round_to = significant_digits - digits - rhs = max(round_to, 0) - lhs = max(math.floor(math.log(n, 10))+1, 1) - return n, round_to, (lhs, rhs), PREFIXES[prefix_level] - else: - s = str(n) - lhs = s.index(".") - rhs = len(s)-s.index(".")-1 - return n, 3, (lhs, rhs), PREFIXES[prefix_level] - - -def autoscale_number(n, significant_digits=None): - n, round_to, _, prefix = scaleinfo(n, significant_digits) - n = round(n, round_to) - fmt_num = "{{:.{}f}}".format(max(round_to, 0)) - fmt = "{} {{prefix}}".format(fmt_num) - return fmt.format( - n, - prefix=prefix - ) - - -class Accumulator: - def __init__(self): - super().__init__() - self.items = [] - self.total = 0 - self.unit = None - - def add(self, value): - self.items.append(value) - self.total += value - - def set_unit(self, unit): - if self.unit is not None and self.unit != unit: - raise RuntimeError( - "attempt to change unit of accumulator" - ) - self.unit = unit - - @property - def average(self): - return self.total / self.total_runs - - @property - def max(self): - return max(self.items) - - @property - def min(self): - return min(self.items) - - @property - def stddev(self): - return math.sqrt(self.variance) - - @property - def variance(self): - avg = self.average - accum = 0 - for value in self.items: - accum += (value - avg)**2 - - return accum / len(self.items) - - @property - def total_runs(self): - return len(self.items) - - def infodict(self): - return { - "nsamples": self.total_runs, - "avg": self.average, - "total": self.total, - "stddev": self.stddev, - "min": self.min, - "max": self.max, - } - - @property - def structured_avg(self): - avg = self.average - stddev = self.stddev - if stddev == 0: - digits = None - else: - digits = math.ceil(math.log(avg / stddev, 10)) - return scaleinfo(avg, digits) + (self.unit,) - - def __str__(self): - avg = self.average - stddev = self.stddev - if stddev == 0: - digits = None - else: - digits = math.ceil(math.log(avg / stddev, 10)) - return "nsamples: {}; average: {}{}".format( - self.total_runs, - autoscale_number(avg, digits), - self.unit or "" - ) - - -class Timer: - start = None - end = None - - @property - def elapsed(self): - if self.end is None or self.start is None: - raise RuntimeError("timer is still running") - return self.end - self.start - - -@contextlib.contextmanager -def timed(key=None): - timer = Timer() - t0 = time.monotonic() - try: - yield timer - finally: - t1 = time.monotonic() - timer.start = t0 - timer.end = t1 - if key is not None: - accum = _registry[key] - accum.add(timer.elapsed) - accum.set_unit("s") - - -def record(key, value, unit): - accum = _registry[key] - accum.set_unit(unit) - accum.add(value) - - -def times(n, pass_iteration=False): - if n < 1: - raise ValueError( - "times decorator needs at least one iteration" - ) - - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - base_kwargs = kwargs - for i in range(n-1): - if pass_iteration: - kwargs = dict(base_kwargs) - kwargs["iteration"] = i - f(*args, **kwargs) - if pass_iteration: - kwargs = dict(base_kwargs) - kwargs["iteration"] = n-1 - return f(*args, **kwargs) - return wrapper - - return decorator - - -class BenchmarkPlugin(Plugin): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def options(self, options, env=os.environ): - options.add_option( - "--benchmark-report", - dest="aioxmpp_bench_report", - default=None, - metavar="FILE", - help="File to save the report to", - ) - options.add_option( - "--benchmark-eventloop", - dest="aioxmpp_eventloop", - default=None, - metavar="CLASS", - help="Event loop policy class to use", - ) - - def configure(self, options, conf): - self.enabled = True - self.report_filename = options.aioxmpp_bench_report - if options.aioxmpp_eventloop is not None: - module_name, cls_name = options.aioxmpp_eventloop.rsplit(".", 1) - module = importlib.import_module(module_name) - cls = getattr(module, cls_name)() - asyncio.set_event_loop_policy(cls) - asyncio.set_event_loop(asyncio.new_event_loop()) - - def report(self, stream): - data = {} - table = [] - for key, info in sorted(_registry.items(), key=lambda x: x[0]): - if not info.total_runs: - continue - table.append( - ( - ".".join(key[:2]), - "/".join(key[2:]), - info.total_runs, - info.structured_avg, - ), - ) - data[key] = info.infodict() - - table.sort() - c12len = max(len(c1)+len(c2)+2 for c1, c2, *_ in table) - c12fmt = "{{:<{}s}}".format(c12len) - c3len = max(math.floor(math.log10(v)) + 1 - for _, _, v, *_ in table) - c3fmt = "{{:>{}d}}".format(c3len) - c4lhs = max(lhs for _, _, _, (_, _, (lhs, _), _, _) in table) - c4rhs = max(rhs for _, _, _, (_, _, (_, rhs), _, _) in table) - for c1, c2, c3, (v, round_to, (lhs, rhs), prefix, unit) in table: - c4numberfmt = "{{:{}.{}f}}".format( - lhs+rhs+1, - rhs - ) - if rhs == 0: - lhs += 1 - c4num = "".join([ - " "*(c4lhs-lhs), - c4numberfmt.format(v), - "." if rhs == 0 else "", - " "*(c4rhs-rhs) - ]) - - print( - c12fmt.format("{} {}".format(c1, c2)), - c3fmt.format(c3), - "{} {}{}".format( - c4num, - prefix or " ", - unit, - ), - sep=" ", - file=stream - ) - - if self.report_filename is not None: - with open(self.report_filename, "w") as f: - f.write(repr(data)) - - -_registry = collections.defaultdict(Accumulator) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__main__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__main__.py deleted file mode 100644 index 98d6acf..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__main__.py +++ /dev/null @@ -1,25 +0,0 @@ -######################################################################## -# File name: __main__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import nose -from aioxmpp.benchtest import BenchmarkPlugin - -nose.main(addplugins=[BenchmarkPlugin()]) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index de571f2..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__pycache__/__main__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__pycache__/__main__.cpython-311.pyc deleted file mode 100644 index 1483a28..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/benchtest/__pycache__/__main__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__init__.py deleted file mode 100644 index 0bef402..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.blocking` --- Blocking Command support (:xep:`0191`) -################################################################### - -This subpackage provides client side support for :xep:`0191`. - -The public interface of this package consists of a single -:class:`~aioxmpp.Service`: - -.. currentmodule:: aioxmpp - -.. autoclass:: BlockingClient - -.. currentmodule:: aioxmpp.blocking - -""" -from .service import BlockingClient # NOQA: F401 diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index b45a359..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/service.cpython-311.pyc deleted file mode 100644 index f9c3b30..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index 754b40d..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/service.py deleted file mode 100644 index 6f211df..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/service.py +++ /dev/null @@ -1,236 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio - -import aioxmpp -import aioxmpp.callbacks as callbacks -import aioxmpp.service as service - -from aioxmpp.utils import namespaces - -from . import xso as blocking_xso - - -class BlockingClient(service.Service): - """ - A :class:`~aioxmpp.service.Service` implementing :xep:`Blocking - Command <191>`. - - This service maintains the list of blocked JIDs and allows - manipulating the blocklist. - - Attribute: - - .. autoattribute:: blocklist - - Signals: - - .. signal:: on_initial_blocklist_received(blocklist) - - Fires when the initial blocklist was received from the server. - - :param blocklist: the initial blocklist - :type blocklist: :class:`~collections.abc.Set` of :class:`~aioxmpp.JID` - - .. signal:: on_jids_blocked(blocked_jids) - - Fires when additional JIDs are blocked. - - :param blocked_jids: the newly blocked JIDs - :type blocked_jids: :class:`~collections.abc.Set` - of :class:`~aioxmpp.JID` - - .. signal:: on_jids_blocked(blocked_jids) - - Fires when JIDs are unblocked. - - :param unblocked_jids: the now unblocked JIDs - :type unblocked_jids: :class:`~collections.abc.Set` - of :class:`~aioxmpp.JID` - - Coroutine methods: - - .. automethod:: block_jids - - .. automethod:: unblock_jids - - .. automethod:: unblock_all - """ - ORDER_AFTER = [aioxmpp.DiscoClient] - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - self._blocklist = None - self._lock = asyncio.Lock() - self._disco = self.dependencies[aioxmpp.DiscoClient] - - on_jids_blocked = callbacks.Signal() - on_jids_unblocked = callbacks.Signal() - on_initial_blocklist_received = callbacks.Signal() - - async def _check_for_blocking(self): - server_info = await self._disco.query_info( - self.client.local_jid.replace( - resource=None, - localpart=None, - ) - ) - - if namespaces.xep0191 not in server_info.features: - self._blocklist = None - raise RuntimeError("server does not support blocklists!") - - @service.depsignal(aioxmpp.Client, "before_stream_established") - async def _get_initial_blocklist(self): - try: - await self._check_for_blocking() - except RuntimeError: - self.logger.info( - "server does not support block lists, skipping initial fetch" - ) - return True - - if self._blocklist is None: - async with self._lock: - iq = aioxmpp.IQ( - type_=aioxmpp.IQType.GET, - payload=blocking_xso.BlockList(), - ) - result = await self.client.send(iq) - self._blocklist = frozenset(result.items) - self.on_initial_blocklist_received(self._blocklist) - - return True - - @property - def blocklist(self): - """ - :class:`~collections.abc.Set` of JIDs blocked by the account. - """ - return self._blocklist - - async def block_jids(self, jids_to_block): - """ - Add the JIDs in the sequence `jids_to_block` to the client's - blocklist. - """ - await self._check_for_blocking() - - if not jids_to_block: - return - - cmd = blocking_xso.BlockCommand(jids_to_block) - iq = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - payload=cmd, - ) - await self.client.send(iq) - - async def unblock_jids(self, jids_to_unblock): - """ - Remove the JIDs in the sequence `jids_to_block` from the - client's blocklist. - """ - await self._check_for_blocking() - - if not jids_to_unblock: - return - - cmd = blocking_xso.UnblockCommand(jids_to_unblock) - iq = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - payload=cmd, - ) - await self.client.send(iq) - - async def unblock_all(self): - """ - Unblock all JIDs currently blocked. - """ - await self._check_for_blocking() - - cmd = blocking_xso.UnblockCommand() - iq = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - payload=cmd, - ) - await self.client.send(iq) - - @service.iq_handler(aioxmpp.IQType.SET, blocking_xso.BlockCommand) - async def handle_block_push(self, block_command): - diff = () - async with self._lock: - if self._blocklist is None: - # this means the stream was destroyed while we were waiting for - # the lock/while the handler was enqueued for scheduling, or - # the server is buggy and sends pushes before we fetched the - # blocklist - return - - if (block_command.from_ is None or - block_command.from_ == self.client.local_jid.bare() or - # WORKAROUND: ejabberd#2287 - block_command.from_ == self.client.local_jid): - diff = frozenset(block_command.payload.items) - self._blocklist |= diff - else: - self.logger.debug( - "received block push from unauthorized JID: %s", - block_command.from_, - ) - - if diff: - self.on_jids_blocked(diff) - - @service.iq_handler(aioxmpp.IQType.SET, blocking_xso.UnblockCommand) - async def handle_unblock_push(self, unblock_command): - diff = () - async with self._lock: - if self._blocklist is None: - # this means the stream was destroyed while we were waiting for - # the lock/while the handler was enqueued for scheduling, or - # the server is buggy and sends pushes before we fetched the - # blocklist - return - - if (unblock_command.from_ is None or - unblock_command.from_ == self.client.local_jid.bare() or - # WORKAROUND: ejabberd#2287 - unblock_command.from_ == self.client.local_jid): - if not unblock_command.payload.items: - diff = frozenset(self._blocklist) - self._blocklist = frozenset() - else: - diff = frozenset(unblock_command.payload.items) - self._blocklist -= diff - else: - self.logger.debug( - "received unblock push from unauthorized JID: %s", - unblock_command.from_, - ) - if diff: - self.on_jids_unblocked(diff) - - @service.depsignal(aioxmpp.stream.StanzaStream, - "on_stream_destroyed") - def handle_stream_destroyed(self, reason): - self._blocklist = None diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/xso.py deleted file mode 100644 index 99935d8..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/blocking/xso.py +++ /dev/null @@ -1,113 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import aioxmpp -import aioxmpp.xso - -from aioxmpp.utils import namespaces - -namespaces.xep0191 = "urn:xmpp:blocking" - - -# this XSO represents a single block list item. -class BlockItem(aioxmpp.xso.XSO): - # define the tag we are matching for - # tags consist of an XML namespace URI and an XML element - TAG = (namespaces.xep0191, "item") - - # bind the ``jid`` python attribute to refer to the ``jid`` XML attribute. - # in addition, automatic conversion between actual JID objects and XML - # character data is requested by specifying the `type_` argument as - # xso.JID() object. - jid = aioxmpp.xso.Attr( - "jid", - type_=aioxmpp.xso.JID() - ) - - -# we now declare a custom type to convert between JID objects and BlockItem -# instances. -# we can use this custom type together with xso.ChildValueList to access the -# list of elements like a normal python list -# of JIDs. -class BlockItemType(aioxmpp.xso.AbstractElementType): - # unpack converts from the "raw" XSO to the - # "rich" python representation, in this case a JID object - # think of unpack like of a high-level struct.unpack: we convert - # wire-format (XML trees) to python values - def unpack(self, item): - return item.jid - - # pack is the reverse operation of unpack - def pack(self, jid): - item = BlockItem() - item.jid = jid - return item - - # we have to tell the XSO framework what XSO types are supported by this - # element type - def get_xso_types(self): - return [BlockItem] - - -# the decorator tells the IQ stanza class that this is a valid payload; that is -# required to be able to *receive* payloads of this type (sending works without -# that decorator, but is not recommended) -@aioxmpp.stanza.IQ.as_payload_class -class BlockList(aioxmpp.xso.XSO): - TAG = (namespaces.xep0191, "blocklist") - - # this does not get an __init__ method, since the client never - # creates a BlockList with entries. - - # xso.ChildValueList uses an AbstractElementType (like the one we defined - # above) to convert between child XSO instances and other python objects. - # it is accessed like a normal list, but when parsing/serialising, the - # elements are converted to XML structures using the given type. - items = aioxmpp.xso.ChildValueList( - BlockItemType() - ) - - -@aioxmpp.stanza.IQ.as_payload_class -class BlockCommand(aioxmpp.xso.XSO): - TAG = (namespaces.xep0191, "block") - - def __init__(self, jids_to_block=None): - if jids_to_block is not None: - self.items[:] = jids_to_block - - items = aioxmpp.xso.ChildValueList( - BlockItemType() - ) - - -@aioxmpp.stanza.IQ.as_payload_class -class UnblockCommand(aioxmpp.xso.XSO): - TAG = (namespaces.xep0191, "unblock") - - def __init__(self, jids_to_block=None): - if jids_to_block is not None: - self.items[:] = jids_to_block - - items = aioxmpp.xso.ChildValueList( - BlockItemType() - ) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__init__.py deleted file mode 100644 index 84f48d3..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__init__.py +++ /dev/null @@ -1,76 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.bookmarks` – Bookmark support (:xep:`0048`) -########################################################## - -This module provides support for storing and retrieving bookmarks on -the server as per :xep:`Bookmarks <48>`. - -Service -======= - -.. currentmodule:: aioxmpp - -.. autoclass:: BookmarkClient - -.. currentmodule:: aioxmpp.bookmarks - -XSOs -==== - -All bookmark types must adhere to the following ABC: - -.. autoclass:: Bookmark - -The following XSOs are used to represent an manipulate bookmark lists. - -.. autoclass:: Conference - -.. autoclass:: URL - -To register custom bookmark classes use: - -.. autofunction:: as_bookmark_class - -The following is used internally as the XSO container for bookmarks. - -.. autoclass:: Storage - -Notes on usage -============== - -.. currentmodule:: aioxmpp - -It is highly recommended to interact with the bookmark client via the -provided signals and the get-modify-set methods -:meth:`~BookmarkClient.add_bookmark`, -:meth:`~BookmarkClient.discard_bookmark` and -:meth:`~BookmarkClient.update_bookmark`. Using -:meth:`~BookmarkClient.set_bookmarks` directly is error prone and -might cause data loss due to race conditions. - -""" - -from .xso import (Storage, Bookmark, Conference, URL, # NOQA: F401 - as_bookmark_class) -from .service import BookmarkClient # NOQA: F401 diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 949eebd..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/service.cpython-311.pyc deleted file mode 100644 index 20af433..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index 8668840..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/service.py deleted file mode 100644 index 6f538aa..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/service.py +++ /dev/null @@ -1,470 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio - -import aioxmpp -import aioxmpp.callbacks as callbacks -import aioxmpp.service as service -import aioxmpp.private_xml as private_xml - -from . import xso as bookmark_xso - - -# TODO: use private storage in pubsub where available. -# TODO: sync bookmarks between pubsub and private xml storage -# TODO: do we need merge-capabilities to reconcile the bookmarks -# from different sources (local bookmark storage, pubsub, private xml -# storage) -class BookmarkClient(service.Service): - """ - Supports retrieval and storage of bookmarks on the server. - It currently only supports :xep:`Private XML Storage <49>` as - backend. - - There is the general rule *never* to modify the bookmark instances - retrieved from this class (either by :meth:`get_bookmarks` or as - an argument to one of the signals). If you need to modify a bookmark - for use with :meth:`update_bookmark` use :func:`copy.copy` to create - a copy. - - .. automethod:: sync - - .. automethod:: get_bookmarks - - .. automethod:: set_bookmarks - - The following methods change the bookmark list in a get-modify-set - pattern, to mitigate the danger of race conditions and should be - used in most circumstances: - - .. automethod:: add_bookmark - - .. automethod:: discard_bookmark - - .. automethod:: update_bookmark - - - The following signals are provided that allow tracking the changes to - the bookmark list: - - .. signal:: on_bookmark_added(added_bookmark) - - Fires when a new bookmark is added. - - .. signal:: on_bookmark_removed(removed_bookmark) - - Fires when a bookmark is removed. - - .. signal:: on_bookmark_changed(old_bookmark, new_bookmark) - - Fires when a bookmark is changed. - - .. note:: A heuristic is used to determine the change of bookmarks - and the reported changes may not directly reflect the - used methods, but it will always be possible to - construct the list of bookmarks from the events. For - example, when using :meth:`update_bookmark` to change - the JID of a :class:`Conference` bookmark a removed and - a added signal will fire. - - .. note:: The bookmark protocol is prone to race conditions if - several clients access it concurrently. Be careful to - use a get-modify-set pattern or the provided highlevel - interface. - - .. note:: Some other clients extend the bookmark format. For now - those extensions are silently dropped by our XSOs, and - therefore are lost, when changing the bookmarks with - aioxmpp. This is considered a bug to be fixed in the future. - """ - - ORDER_AFTER = [ - private_xml.PrivateXMLService, - ] - - on_bookmark_added = callbacks.Signal() - on_bookmark_removed = callbacks.Signal() - on_bookmark_changed = callbacks.Signal() - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - self._private_xml = self.dependencies[private_xml.PrivateXMLService] - self._bookmark_cache = [] - self._lock = asyncio.Lock() - - @service.depsignal(aioxmpp.Client, "on_stream_established", defer=True) - async def _stream_established(self): - await self.sync() - - async def _get_bookmarks(self): - """ - Get the stored bookmarks from the server. - - :returns: a list of bookmarks - """ - res = await self._private_xml.get_private_xml( - bookmark_xso.Storage() - ) - - return res.registered_payload.bookmarks - - async def _set_bookmarks(self, bookmarks): - """ - Set the bookmarks stored on the server. - """ - storage = bookmark_xso.Storage() - storage.bookmarks[:] = bookmarks - await self._private_xml.set_private_xml(storage) - - def _diff_emit_update(self, new_bookmarks): - """ - Diff the bookmark cache and the new bookmark state, emit signals as - needed and set the bookmark cache to the new data. - """ - - self.logger.debug("diffing %s, %s", self._bookmark_cache, - new_bookmarks) - - def subdivide(level, old, new): - """ - Subdivide the bookmarks according to the data item - ``bookmark.secondary[level]`` and emit the appropriate - events. - """ - if len(old) == len(new) == 1: - old_entry = old.pop() - new_entry = new.pop() - if old_entry == new_entry: - pass - else: - self.on_bookmark_changed(old_entry, new_entry) - return ([], []) - - elif len(old) == 0: - return ([], new) - - elif len(new) == 0: - return (old, []) - - else: - try: - groups = {} - for entry in old: - group = groups.setdefault( - entry.secondary[level], - ([], []) - ) - group[0].append(entry) - - for entry in new: - group = groups.setdefault( - entry.secondary[level], - ([], []) - ) - group[1].append(entry) - except IndexError: - # the classification is exhausted, this means - # all entries in this bin are equal by the - # definition of bookmark equivalence! - common = min(len(old), len(new)) - assert old[:common] == new[:common] - return (old[common:], new[common:]) - - old_unhandled, new_unhandled = [], [] - for old, new in groups.values(): - unhandled = subdivide(level+1, old, new) - old_unhandled += unhandled[0] - new_unhandled += unhandled[1] - - # match up unhandleds as changes as early as possible - i = -1 - for i, (old_entry, new_entry) in enumerate( - zip(old_unhandled, new_unhandled)): - self.logger.debug("changed %s -> %s", old_entry, new_entry) - self.on_bookmark_changed(old_entry, new_entry) - i += 1 - return old_unhandled[i:], new_unhandled[i:] - - # group the bookmarks into groups whose elements may transform - # among one another by on_bookmark_changed events. This information - # is given by the type of the bookmark and the .primary property - changable_groups = {} - - for item in self._bookmark_cache: - group = changable_groups.setdefault( - (type(item), item.primary), - ([], []) - ) - group[0].append(item) - - for item in new_bookmarks: - group = changable_groups.setdefault( - (type(item), item.primary), - ([], []) - ) - group[1].append(item) - - for old, new in changable_groups.values(): - - # the first branches are fast paths which should catch - # most cases – especially all cases where each bare jid of - # a conference bookmark or each url of an url bookmark is - # only used in one bookmark - if len(old) == len(new) == 1: - old_entry = old.pop() - new_entry = new.pop() - if old_entry == new_entry: - # the bookmark is unchanged, do not emit an event - pass - else: - self.logger.debug("changed %s -> %s", old_entry, new_entry) - self.on_bookmark_changed(old_entry, new_entry) - elif len(new) == 0: - for removed in old: - self.logger.debug("removed %s", removed) - self.on_bookmark_removed(removed) - elif len(old) == 0: - for added in new: - self.logger.debug("added %s", added) - self.on_bookmark_added(added) - else: - old, new = subdivide(0, old, new) - - assert len(old) == 0 or len(new) == 0 - - for removed in old: - self.logger.debug("removed %s", removed) - self.on_bookmark_removed(removed) - - for added in new: - self.logger.debug("added %s", added) - self.on_bookmark_added(added) - - self._bookmark_cache = new_bookmarks - - async def get_bookmarks(self): - """ - Get the stored bookmarks from the server. Causes signals to be - fired to reflect the changes. - - :returns: a list of bookmarks - """ - async with self._lock: - bookmarks = await self._get_bookmarks() - self._diff_emit_update(bookmarks) - return bookmarks - - async def set_bookmarks(self, bookmarks): - """ - Store the sequence of bookmarks `bookmarks`. - - Causes signals to be fired to reflect the changes. - - .. note:: This should normally not be used. It does not - mitigate the race condition between clients - concurrently modifying the bookmarks and may lead to - data loss. Use :meth:`add_bookmark`, - :meth:`discard_bookmark` and :meth:`update_bookmark` - instead. This method still has use-cases (modifying - the bookmarklist at large, e.g. by syncing the - remote store with local data). - """ - async with self._lock: - await self._set_bookmarks(bookmarks) - self._diff_emit_update(bookmarks) - - async def sync(self): - """ - Sync the bookmarks between the local representation and the - server. - - This must be called periodically to assure that the signals - are fired. - """ - await self.get_bookmarks() - - async def add_bookmark(self, new_bookmark, *, max_retries=3): - """ - Add a bookmark and check whether it was successfully added to the - bookmark list. Already existent bookmarks are not added twice. - - :param new_bookmark: the bookmark to add - :type new_bookmark: an instance of :class:`~bookmark_xso.Bookmark` - :param max_retries: the number of retries if setting the bookmark - fails - :type max_retries: :class:`int` - - :raises RuntimeError: if the bookmark is not in the bookmark list - after `max_retries` retries. - - After setting the bookmark it is checked, whether the bookmark - is in the online storage, if it is not it is tried again at most - `max_retries` times to add the bookmark. A :class:`RuntimeError` - is raised if the bookmark could not be added successfully after - `max_retries`. - """ - async with self._lock: - bookmarks = await self._get_bookmarks() - - try: - modified_bookmarks = list(bookmarks) - if new_bookmark not in bookmarks: - modified_bookmarks.append(new_bookmark) - await self._set_bookmarks(modified_bookmarks) - - retries = 0 - bookmarks = await self._get_bookmarks() - while retries < max_retries: - if new_bookmark in bookmarks: - break - modified_bookmarks = list(bookmarks) - modified_bookmarks.append(new_bookmark) - await self._set_bookmarks(modified_bookmarks) - bookmarks = await self._get_bookmarks() - retries += 1 - - if new_bookmark not in bookmarks: - raise RuntimeError("Could not add bookmark") - - finally: - self._diff_emit_update(bookmarks) - - async def discard_bookmark(self, bookmark_to_remove, *, max_retries=3): - """ - Remove a bookmark and check it has been removed. - - :param bookmark_to_remove: the bookmark to remove - :type bookmark_to_remove: a :class:`~bookmark_xso.Bookmark` subclass. - :param max_retries: the number of retries of removing the bookmark - fails. - :type max_retries: :class:`int` - - :raises RuntimeError: if the bookmark is not removed from - bookmark list after `max_retries` - retries. - - If there are multiple occurrences of the same bookmark exactly - one is removed. - - This does nothing if the bookmarks does not match an existing - bookmark according to bookmark-equality. - - After setting the bookmark it is checked, whether the bookmark - is removed in the online storage, if it is not it is tried - again at most `max_retries` times to remove the bookmark. A - :class:`RuntimeError` is raised if the bookmark could not be - removed successfully after `max_retries`. - """ - async with self._lock: - bookmarks = await self._get_bookmarks() - occurrences = bookmarks.count(bookmark_to_remove) - - try: - if not occurrences: - return - - modified_bookmarks = list(bookmarks) - modified_bookmarks.remove(bookmark_to_remove) - await self._set_bookmarks(modified_bookmarks) - - retries = 0 - bookmarks = await self._get_bookmarks() - new_occurences = bookmarks.count(bookmark_to_remove) - while retries < max_retries: - if new_occurences < occurrences: - break - modified_bookmarks = list(bookmarks) - modified_bookmarks.remove(bookmark_to_remove) - await self._set_bookmarks(modified_bookmarks) - bookmarks = await self._get_bookmarks() - new_occurences = bookmarks.count(bookmark_to_remove) - retries += 1 - - if new_occurences >= occurrences: - raise RuntimeError("Could not remove bookmark") - finally: - self._diff_emit_update(bookmarks) - - async def update_bookmark(self, old, new, *, max_retries=3): - """ - Update a bookmark and check it was successful. - - The bookmark matches an existing bookmark `old` according to - bookmark equalitiy and replaces it by `new`. The bookmark - `new` is added if no bookmark matching `old` exists. - - :param old: the bookmark to replace - :type bookmark_to_remove: a :class:`~bookmark_xso.Bookmark` subclass. - :param new: the replacement bookmark - :type bookmark_to_remove: a :class:`~bookmark_xso.Bookmark` subclass. - :param max_retries: the number of retries of removing the bookmark - fails. - :type max_retries: :class:`int` - - :raises RuntimeError: if the bookmark is not in the bookmark list - after `max_retries` retries. - - After replacing the bookmark it is checked, whether the - bookmark `new` is in the online storage, if it is not it is - tried again at most `max_retries` times to replace the - bookmark. A :class:`RuntimeError` is raised if the bookmark - could not be replaced successfully after `max_retries`. - - .. note:: Do not modify a bookmark retrieved from the signals - or from :meth:`get_bookmarks` to obtain the bookmark - `new`, this will lead to data corruption as they are - passed by reference. Instead use :func:`copy.copy` - and modify the copy. - - """ - def replace_bookmark(bookmarks, old, new): - modified_bookmarks = list(bookmarks) - try: - i = bookmarks.index(old) - modified_bookmarks[i] = new - except ValueError: - modified_bookmarks.append(new) - return modified_bookmarks - - async with self._lock: - bookmarks = await self._get_bookmarks() - - try: - await self._set_bookmarks( - replace_bookmark(bookmarks, old, new) - ) - - retries = 0 - bookmarks = await self._get_bookmarks() - while retries < max_retries: - if new in bookmarks: - break - await self._set_bookmarks( - replace_bookmark(bookmarks, old, new) - ) - bookmarks = await self._get_bookmarks() - retries += 1 - - if new not in bookmarks: - raise RuntimeError("Cold not update bookmark") - finally: - self._diff_emit_update(bookmarks) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/xso.py deleted file mode 100644 index 2201834..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/bookmarks/xso.py +++ /dev/null @@ -1,262 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -from abc import abstractproperty - -import aioxmpp.private_xml as private_xml -import aioxmpp.xso as xso - - -from aioxmpp.utils import namespaces - - -namespaces.xep0048 = "storage:bookmarks" - - -class Bookmark(xso.XSO): - """ - A bookmark XSO abstract base class. - - Every XSO class registered as child of :class:`Storage` must be - a :class:`Bookmark` subclass. - - Bookmarks must provide the following interface: - - .. autoattribute:: primary - - .. autoattribute:: secondary - - .. autoattribute:: name - - Equality is defined in terms of those properties: - - .. automethod:: __eq__ - - It is highly recommended not to redefine :meth:`__eq__` in a - subclass, if you do so make sure that the following axiom - relating :meth:`__eq__`, :attr:`primary` and :attr:`secondary` - holds:: - - (type(a) == type(b) and - a.primary == b.primary and - a.secondary == b.secondary) - - if and only if:: - - a == b - - Otherwise the generation of bookmark change signals is not - guaranteed to be correct. - """ - - def __eq__(self, other): - """ - Compare for equality by value and type. - - The value of a bookmark must be fully determined by the values - of the :attr:`primary` and :attr:`secondary` properties. - - This is used for generating the bookmark list change signals - and for the get-modify-set methods. - """ - return (type(self) == type(other) and - self.primary == other.primary and - self.secondary == other.secondary) - - @abstractproperty - def primary(self): - """ - Return the primary category of the bookmark. - - The internal structure of the category is opaque to the code - using it; only equality and hashing must be provided and - operate by value. It is recommended that this be either a - single datum (e.g. a string or JID) or a tuple of data items. - - Together with the type and :attr:`secondary` this must *fully* - determine the value of the bookmark. - - This is used in the computation of the change - signals. Bookmarks with different type or :attr:`primary` - keys cannot be identified as changed from/to one another. - """ - raise NotImplementedError # pragma: no cover - - @abstractproperty - def secondary(self): - """ - Return the tuple of secondary categories of the bookmark. - - Together with the type and :attr:`primary` they must *fully* - determine the value of the bookmark. - - This is used in the computation of the change signals. The - categories in the tuple are ordered in decreasing precedence, - when calculating which bookmarks have changed the ones which - mismatch in the category with the lowest precedence are - grouped together. - - The length of the tuple must be the same for all bookmarks of - a type. - """ - raise NotImplementedError # pragma: no cover - - @abstractproperty - def name(self): - """ - The human-readable label or description of the bookmark. - """ - raise NotImplementedError # pragma: no cover - - -class Conference(Bookmark): - """ - An bookmark for a groupchat. - - .. attribute:: name - - The name of the bookmark. - - .. attribute:: jid - - The jid under which the groupchat is accessible. - - .. attribute:: autojoin - - Whether to join automatically, when the client starts. - - .. attribute:: nick - - The nick to use in the groupchat. - - .. attribute:: password - - The password used to access the groupchat. - """ - - TAG = (namespaces.xep0048, "conference") - - autojoin = xso.Attr(tag="autojoin", type_=xso.Bool(), default=False) - jid = xso.Attr(tag="jid", type_=xso.JID()) - name = xso.Attr(tag="name", type_=xso.String(), default=None) - - nick = xso.ChildText( - (namespaces.xep0048, "nick"), - default=None - ) - password = xso.ChildText( - (namespaces.xep0048, "password"), - default=None - ) - - def __init__(self, name, jid, *, autojoin=False, nick=None, password=None): - self.autojoin = autojoin - self.jid = jid - self.name = name - self.nick = nick - self.password = password - - def __repr__(self): - return "Conference({!r}, {!r}, autojoin={!r}, " \ - "nick={!r}, password{!r})".\ - format(self.name, self.jid, self.autojoin, self.nick, - self.password) - - @property - def primary(self): - return self.jid - - @property - def secondary(self): - return (self.name, self.nick, self.password, self.autojoin) - - -class URL(Bookmark): - """ - An URL bookmark. - - .. attribute:: name - - The name of the bookmark. - - .. attribute:: url - - The URL the bookmark saves. - """ - TAG = (namespaces.xep0048, "url") - - name = xso.Attr(tag="name", type_=xso.String(), default=None) - # XXX: we might want to use a URL type once we have one - url = xso.Attr(tag="url", type_=xso.String()) - - def __init__(self, name, url): - self.name = name - self.url = url - - def __repr__(self): - return "URL({!r}, {!r})".format(self.name, self.url) - - @property - def primary(self): - return self.url - - @property - def secondary(self): - return (self.name,) - - -@private_xml.Query.as_payload_class -class Storage(xso.XSO): - """ - The container for storing bookmarks. - - .. attribute:: bookmarks - - A :class:`~xso.XSOList` of bookmarks. - """ - - TAG = (namespaces.xep0048, "storage") - - bookmarks = xso.ChildList([URL, Conference]) - - -def as_bookmark_class(xso_class): - """ - Decorator to register `xso_class` as a custom bookmark class. - - This is necessary to store and retrieve such bookmarks. - The registered class must be a subclass of the abstract base class - :class:`Bookmark`. - - :raises TypeError: if `xso_class` is not a subclass of :class:`Bookmark`. - """ - - if not issubclass(xso_class, Bookmark): - raise TypeError( - "Classes registered as bookmark types must be Bookmark subclasses" - ) - - Storage.register_child( - Storage.bookmarks, - xso_class - ) - - return xso_class diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/cache.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/cache.py deleted file mode 100644 index 49cf0b3..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/cache.py +++ /dev/null @@ -1,179 +0,0 @@ -######################################################################## -# File name: cache.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.cache` --- Utilities for implementing caches -########################################################### - -.. versionadded:: 0.9 - - This module was added in version 0.9. - -.. autoclass:: LRUDict - -""" - -import collections.abc - - -class Node: - __slots__ = ("prev", "next_", "key", "value") - - -def _init_linked_list(): - root = Node() - root.prev = root - root.next_ = root - root.key = None - root.value = None - return root - - -def _remove_node(node): - node.next_.prev = node.prev - node.prev.next_ = node.next_ - return node - - -def _insert_node(before, new_node): - new_node.next_ = before.next_ - new_node.next_.prev = new_node - new_node.prev = before - before.next_ = new_node - - -def _length(node): - # this is used only for testing - cur = node.next_ - i = 0 - while cur is not node: - i += 1 - cur = cur.next_ - return i - - -def _has_consistent_links(node, node_dict=None): - # this is used only for testing - cur = node.next_ - - if cur.prev is not node: - return False - - while cur is not node: - if node_dict is not None and node_dict[cur.key] is not cur: - return False - if cur is not cur.next_.prev: - return False - cur = cur.next_ - return True - - -class LRUDict(collections.abc.MutableMapping): - """ - Size-restricted dictionary with Least Recently Used expiry policy. - - .. versionadded:: 0.9 - - The :class:`LRUDict` supports normal dictionary-style access and implements - :class:`collections.abc.MutableMapping`. - - When the :attr:`maxsize` is exceeded, as many entries as needed to get - below the :attr:`maxsize` are removed from the dict. Least recently used - entries are purged first. Setting an entry does *not* count as use! - - .. autoattribute:: maxsize - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.__links = {} - self.__root = _init_linked_list() - - self.__maxsize = 1 - - def _test_consistency(self): - """ - This method is only used for testing to assert that the operations - leave the LRUDict in a valid state. - """ - return (_length(self.__root) == len(self.__links) and - _has_consistent_links(self.__root, self.__links)) - - def _purge(self): - if self.__maxsize is None: - return - - while len(self.__links) > self.__maxsize: - link = _remove_node(self.__root.prev) - del self.__links[link.key] - - @property - def maxsize(self): - """ - Maximum size of the cache. Changing this property purges overhanging - entries immediately. - - If set to :data:`None`, no limit on the number of entries is imposed. - Do **not** use a limit of :data:`None` for data where the `key` is - under control of a remote entity. - - Use cases for :data:`None` are those where you only need the explicit - expiry feature, but not the LRU feature. - """ - return self.__maxsize - - @maxsize.setter - def maxsize(self, value): - if value is not None and value <= 0: - raise ValueError("maxsize must be positive integer or None") - self.__maxsize = value - self._purge() - - def __len__(self): - return len(self.__links) - - def __iter__(self): - return iter(self.__links) - - def __setitem__(self, key, value): - try: - self.__links[key].value = value - except KeyError: - link = Node() - link.key = key - link.value = value - self.__links[key] = link - _insert_node(self.__root, link) - self._purge() - - def __getitem__(self, key): - link = self.__links[key] - _remove_node(link) - _insert_node(self.__root, link) - return link.value - - def __delitem__(self, key): - link = self.__links.pop(key) - _remove_node(link) - - def clear(self): - self.__links.clear() - self.__root = _init_linked_list() diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/callbacks.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/callbacks.py deleted file mode 100644 index 6613963..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/callbacks.py +++ /dev/null @@ -1,895 +0,0 @@ -######################################################################## -# File name: callbacks.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.callbacks` -- Synchronous and asynchronous callbacks -################################################################### - -This module provides facilities for objects to provide signals to which other -objects can connect. - -Descriptor vs. ad-hoc -===================== - -Descriptors can be used as class attributes and will create ad-hoc signals -dynamically for each instance. They are the most commonly used: - -.. code-block:: python - - class Emitter: - on_event = callbacks.Signal() - - def handler(): - pass - - emitter1 = Emitter() - emitter2 = Emitter() - emitter1.on_event.connect(handler) - - emitter1.on_event() # calls `handler` - emitter2.on_event() # does not call `handler` - - # the actual signals are distinct - assert emitter1.on_event is not emitter2.on_event - -Ad-hoc signals are useful for testing and are the type of which the actual -fields are. - -Signal overview -=============== - -.. autosummary:: - - Signal - SyncSignal - AdHocSignal - SyncAdHocSignal - -Utilities ---------- - -.. autofunction:: first_signal - -Signal descriptors ------------------- - -These descriptors can be used on classes to have attributes which are signals: - -.. autoclass:: Signal - -.. autoclass:: SyncSignal - -Signal implementations (ad-hoc signals) ---------------------------------------- - -Whenever accessing an attribute using the :class:`Signal` or -:class:`SyncSignal` descriptors, an object of one of the following classes is -returned. This is where the behaviour of the signals is specified. - -.. autoclass:: AdHocSignal - -.. autoclass:: SyncAdHocSignal - - -Filters -======= - -.. autoclass:: Filter - -""" - -import abc -import asyncio -import collections -import contextlib -import functools -import logging -import types -import weakref - - -logger = logging.getLogger(__name__) - - -def log_spawned(logger, fut): - try: - result = fut.result() - except asyncio.CancelledError: - logger.debug("spawned task was cancelled") - except: # NOQA - logger.warning("spawned task raised exception", exc_info=True) - else: - if result is not None: - logger.info("value returned by spawned task was ignored: %r", - result) - - -class TagListener: - def __init__(self, ondata, onerror=None): - self._ondata = ondata - self._onerror = onerror - - def data(self, data): - return self._ondata(data) - - def error(self, exc): - if self._onerror is not None: - return self._onerror(exc) - - def is_valid(self): - return True - - -class AsyncTagListener(TagListener): - def __init__(self, ondata, onerror=None, *, loop=None): - super().__init__(ondata, onerror) - self._loop = loop or asyncio.get_event_loop() - - def data(self, data): - self._loop.call_soon(self._ondata, data) - - def error(self, exc): - if self._onerror is not None: - self._loop.call_soon(self._onerror, exc) - - -class OneshotTagListener(TagListener): - def __init__(self, ondata, onerror=None, **kwargs): - super().__init__(ondata, onerror=onerror, **kwargs) - self._cancelled = False - - def data(self, data): - super().data(data) - return True - - def error(self, exc): - super().error(exc) - return True - - def cancel(self): - self._cancelled = True - - def is_valid(self): - return not self._cancelled and super().is_valid() - - -class OneshotAsyncTagListener(OneshotTagListener, AsyncTagListener): - pass - - -class FutureListener: - def __init__(self, fut): - self.fut = fut - - def data(self, data): - try: - self.fut.set_result(data) - except asyncio.InvalidStateError: - pass - return True - - def error(self, exc): - try: - self.fut.set_exception(exc) - except asyncio.InvalidStateError: - pass - return True - - def is_valid(self): - return not self.fut.done() - - -class TagDispatcher: - def __init__(self): - self._listeners = {} - - def add_callback(self, tag, fn): - return self.add_listener(tag, TagListener(fn)) - - def add_callback_async(self, tag, fn, *, loop=None): - return self.add_listener( - tag, - AsyncTagListener(fn, loop=loop) - ) - - def add_future(self, tag, fut): - return self.add_listener( - tag, - FutureListener(fut) - ) - - def add_listener(self, tag, listener): - try: - existing = self._listeners[tag] - if not existing.is_valid(): - raise KeyError() - except KeyError: - self._listeners[tag] = listener - else: - raise ValueError("only one listener is allowed per tag") - - def unicast(self, tag, data): - cb = self._listeners[tag] - if not cb.is_valid(): - del self._listeners[tag] - self._listeners[tag] - if cb.data(data): - del self._listeners[tag] - - def unicast_error(self, tag, exc): - cb = self._listeners[tag] - if not cb.is_valid(): - del self._listeners[tag] - self._listeners[tag] - if cb.error(exc): - del self._listeners[tag] - - def remove_listener(self, tag): - del self._listeners[tag] - - def broadcast_error(self, exc): - for tag, listener in list(self._listeners.items()): - if listener.is_valid() and listener.error(exc): - del self._listeners[tag] - - def close_all(self, exc): - self.broadcast_error(exc) - self._listeners.clear() - - -class AbstractAdHocSignal: - def __init__(self): - super().__init__() - self._connections = collections.OrderedDict() - self.logger = logger - - def _connect(self, wrapper): - token = object() - self._connections[token] = wrapper - return token - - def disconnect(self, token): - """ - Disconnect the connection identified by `token`. This never raises, - even if an invalid `token` is passed. - """ - try: - del self._connections[token] - except KeyError: - pass - - -class AdHocSignal(AbstractAdHocSignal): - """ - An ad-hoc signal is a single emitter. This is where callables are connected - to, using the :meth:`connect` method of the :class:`AdHocSignal`. - - .. automethod:: fire - - .. automethod:: connect - - .. automethod:: context_connect - - .. automethod:: future - - .. attribute:: logger - - This may be a :class:`logging.Logger` instance to allow the signal to - log errors and debug events to a specific logger instead of the default - logger (``aioxmpp.callbacks``). - - This attribute must not be :data:`None`, and it is initialised to the - default logger on creation of the :class:`AdHocSignal`. - - The different ways callables can be connected to an ad-hoc signal are shown - below: - - .. attribute:: STRONG - - Connections using this mode keep a strong reference to the callable. The - callable is called directly, thus blocking the emission of the signal. - - .. attribute:: WEAK - - Connections using this mode keep a weak reference to the callable. The - callable is executed directly, thus blocking the emission of the signal. - - If the weak reference is dead, it is automatically removed from the - signals connection list. If the callable is a bound method, - :class:`weakref.WeakMethod` is used automatically. - - For both :attr:`STRONG` and :attr:`WEAK` holds: if the callable returns a - true value, it is disconnected from the signal. - - .. classmethod:: ASYNC_WITH_LOOP(loop) - - This mode requires an :mod:`asyncio` event loop as argument. When the - signal is emitted, the callable is not called directly. Instead, it is - enqueued for calling with the event loop using - :meth:`asyncio.BaseEventLoop.call_soon`. If :data:`None` is passed as - `loop`, the loop is obtained from :func:`asyncio.get_event_loop` at - connect time. - - A strong reference is held to the callable. - - Connections using this mode are never removed automatically from the - signals connection list. You have to use :meth:`disconnect` explicitly. - - .. attribute:: AUTO_FUTURE - - Instead of a callable, a :class:`asyncio.Future` must be passed when - using this mode. - - This mode can only be used for signals which send at most one - positional argument. If no argument is sent, the - :meth:`~asyncio.Future.set_result` method is called with :data:`None`. - - If one argument is sent and it is an instance of :class:`Exception`, it - is passed to :meth:`~asyncio.Future.set_exception`. Otherwise, if one - argument is sent, it is passed to - :meth:`~asyncio.Future.set_exception`. - - In any case, the future is removed after the next emission of the - signal. - - .. classmethod:: SPAWN_WITH_LOOP(loop) - - This mode requires an :mod:`asyncio` event loop as argument and a - coroutine to be passed to :meth:`connect`. If :data:`None` is passed as - `loop`, the loop is obtained from :func:`asyncio.get_event_loop` at - connect time. - - When the signal is emitted, the coroutine is spawned using - :func:`asyncio.ensure_future` in the given `loop`, with the arguments - passed to the signal. - - A strong reference is held to the coroutine. - - Connections using this mode are never removed automatically from the - signals connection list. You have to use :meth:`disconnect` explicitly. - - If the spawned coroutine returns with an exception or a non-:data:`None` - return value, a message is logged, with the following log levels: - - * Return with non-:data:`None` value: :data:`logging.INFO` - * Raises :class:`asyncio.CancelledError`: :data:`logging.DEBUG` - * Raises any other exception: :data:`logging.WARNING` - - .. versionadded:: 0.6 - - .. automethod:: disconnect - - """ - - @classmethod - def STRONG(cls, f): - if not hasattr(f, "__call__"): - raise TypeError("must be callable, got {!r}".format(f)) - return functools.partial(cls._strong_wrapper, f) - - @classmethod - def ASYNC_WITH_LOOP(cls, loop): - if loop is None: - loop = asyncio.get_event_loop() - - def create_wrapper(f): - if not hasattr(f, "__call__"): - raise TypeError("must be callable, got {!r}".format(f)) - return functools.partial(cls._async_wrapper, - f, - loop) - - return create_wrapper - - @classmethod - def WEAK(cls, f): - if not hasattr(f, "__call__"): - raise TypeError("must be callable, got {!r}".format(f)) - if isinstance(f, types.MethodType): - ref = weakref.WeakMethod(f) - else: - ref = weakref.ref(f) - return functools.partial(cls._weakref_wrapper, ref) - - @classmethod - def AUTO_FUTURE(cls, f): - def future_wrapper(args, kwargs): - if len(args) > 0: - try: - arg, = args - except ValueError: - raise TypeError("too many arguments") from None - else: - arg = None - if f.done(): - return - if isinstance(arg, Exception): - f.set_exception(arg) - else: - f.set_result(arg) - return future_wrapper - - @classmethod - def SPAWN_WITH_LOOP(cls, loop): - loop = asyncio.get_event_loop() if loop is None else loop - - def spawn(f): - if not asyncio.iscoroutinefunction(f): - raise TypeError("must be coroutine, got {!r}".format(f)) - - def wrapper(args, kwargs): - task = asyncio.ensure_future(f(*args, **kwargs), loop=loop) - task.add_done_callback( - functools.partial( - log_spawned, - logger, - ) - ) - return True - - return wrapper - - return spawn - - @staticmethod - def _async_wrapper(f, loop, args, kwargs): - if kwargs: - functools.partial(f, *args, **kwargs) - loop.call_soon(f, *args) - return True - - @staticmethod - def _weakref_wrapper(fref, args, kwargs): - f = fref() - if f is None: - return False - return not f(*args, **kwargs) - - @staticmethod - def _strong_wrapper(f, args, kwargs): - return not f(*args, **kwargs) - - def connect(self, f, mode=None): - """ - Connect an object `f` to the signal. The type the object needs to have - depends on `mode`, but usually it needs to be a callable. - - :meth:`connect` returns an opaque token which can be used with - :meth:`disconnect` to disconnect the object from the signal. - - The default value for `mode` is :attr:`STRONG`. Any decorator can be - used as argument for `mode` and it is applied to `f`. The result is - stored internally and is what will be called when the signal is being - emitted. - - If the result of `mode` returns a false value during emission, the - connection is removed. - - .. note:: - - The return values required by the callable returned by `mode` and - the one required by a callable passed to `f` using the predefined - modes are complementary! - - A callable `f` needs to return true to be removed from the - connections, while a callable returned by the `mode` decorator needs - to return false. - - Existing modes are listed below. - """ - - mode = mode or self.STRONG - self.logger.debug("connecting %r with mode %r", f, mode) - return self._connect(mode(f)) - - def context_connect(self, f, mode=None): - """ - This returns a *context manager*. When entering the context, `f` is - connected to the :class:`AdHocSignal` using `mode`. When leaving the - context (no matter whether with or without exception), the connection - is disconnected. - - .. seealso:: - - The returned object is an instance of - :class:`SignalConnectionContext`. - - """ - return SignalConnectionContext(self, f, mode=mode) - - def fire(self, *args, **kwargs): - """ - Emit the signal, calling all connected objects in-line with the given - arguments and in the order they were registered. - - :class:`AdHocSignal` provides full isolation with respect to - exceptions. If a connected listener raises an exception, the other - listeners are executed as normal, but the raising listener is removed - from the signal. The exception is logged to :attr:`logger` and *not* - re-raised, so that the caller of the signal is also not affected. - - Instead of calling :meth:`fire` explicitly, the ad-hoc signal object - itself can be called, too. - """ - for token, wrapper in list(self._connections.items()): - try: - keep = wrapper(args, kwargs) - except Exception: - self.logger.exception("listener attached to signal raised") - keep = False - if not keep: - del self._connections[token] - - def future(self): - """ - Return a :class:`asyncio.Future` which has been :meth:`connect`\\ -ed - using :attr:`AUTO_FUTURE`. - - The token returned by :meth:`connect` is not returned; to remove the - future from the signal, just cancel it. - """ - fut = asyncio.Future() - self.connect(fut, self.AUTO_FUTURE) - return fut - - __call__ = fire - - -class SyncAdHocSignal(AbstractAdHocSignal): - """ - A synchronous ad-hoc signal is like :class:`AdHocSignal`, but for - coroutines instead of ordinary callables. - - .. automethod:: connect - - .. automethod:: context_connect - - .. automethod:: fire - - .. automethod:: disconnect - """ - - def connect(self, coro): - """ - The coroutine `coro` is connected to the signal. The coroutine must - return a true value, unless it wants to be disconnected from the - signal. - - .. note:: - - This is different from the return value convention with - :attr:`AdHocSignal.STRONG` and :attr:`AdHocSignal.WEAK`. - - :meth:`connect` returns a token which can be used with - :meth:`disconnect` to disconnect the coroutine. - """ - self.logger.debug("connecting %r", coro) - return self._connect(coro) - - def context_connect(self, coro): - """ - This returns a *context manager*. When entering the context, `coro` is - connected to the :class:`SyncAdHocSignal`. When leaving the context (no - matter whether with or without exception), the connection is - disconnected. - - .. seealso:: - - The returned object is an instance of - :class:`SignalConnectionContext`. - - """ - return SignalConnectionContext(self, coro) - - async def fire(self, *args, **kwargs): - """ - Emit the signal, calling all coroutines in-line with the given - arguments and in the order they were registered. - - This is obviously a coroutine. - - Instead of calling :meth:`fire` explicitly, the ad-hoc signal object - itself can be called, too. - """ - for token, coro in list(self._connections.items()): - keep = await coro(*args, **kwargs) - if not keep: - del self._connections[token] - - __call__ = fire - - -class SignalConnectionContext: - def __init__(self, signal, *args, **kwargs): - self._signal = signal - self._args = args - self._kwargs = kwargs - - def __enter__(self): - try: - token = self._signal.connect(*self._args, **self._kwargs) - finally: - del self._args - del self._kwargs - self._token = token - return token - - def __exit__(self, exc_type, exc_value, traceback): - self._signal.disconnect(self._token) - return False - - -class AbstractSignal(metaclass=abc.ABCMeta): - def __init__(self, *, doc=None): - super().__init__() - self.__doc__ = doc - self._instances = weakref.WeakKeyDictionary() - - @abc.abstractclassmethod - def make_adhoc_signal(cls): - pass - - def __get__(self, instance, owner): - if instance is None: - return self - try: - return self._instances[instance] - except KeyError: - new = self.make_adhoc_signal() - self._instances[instance] = new - return new - - def __set__(self, instance, value): - raise AttributeError("cannot override Signal attribute") - - def __delete__(self, instance): - raise AttributeError("cannot override Signal attribute") - - -class Signal(AbstractSignal): - """ - A descriptor which returns per-instance :class:`AdHocSignal` objects on - attribute access. - - Example use: - - .. code-block:: python - - class Foo: - on_event = Signal() - - f = Foo() - assert isinstance(f.on_event, AdHocSignal) - assert f.on_event is f.on_event - assert Foo().on_event is not f.on_event - - """ - - @classmethod - def make_adhoc_signal(cls): - return AdHocSignal() - - -class SyncSignal(AbstractSignal): - """ - A descriptor which returns per-instance :class:`SyncAdHocSignal` objects on - attribute access. - - Example use: - - .. code-block:: python - - class Foo: - on_event = SyncSignal() - - f = Foo() - assert isinstance(f.on_event, SyncAdHocSignal) - assert f.on_event is f.on_event - assert Foo().on_event is not f.on_event - """ - - @classmethod - def make_adhoc_signal(cls): - return SyncAdHocSignal() - - -class Filter: - """ - A filter chain for arbitrary data. - - This is used for example in :class:`~.stream.StanzaStream` to allow - services and applications to filter inbound and outbound stanzas. - - Each function registered with the filter receives at least one argument. - This argument is the object which is to be filtered. The function must - return the object, a replacement or :data:`None`. If :data:`None` is - returned, the filter chain aborts and further functions are not called. - Otherwise, the next function is called with the result of the previous - function until the filter chain is complete. - - Other arguments passed to :meth:`filter` are passed unmodified to each - function called; only the first argument is subject to filtering. - - .. versionchanged:: 0.9 - - This class was formerly available at :class:`aioxmpp.stream.Filter`. - - .. automethod:: register - - .. automethod:: filter - - .. automethod:: unregister - - .. automethod:: context_register(func[, order]) - """ - - class Token: - def __str__(self): - return "<{}.{} 0x{:x}>".format( - type(self).__module__, - type(self).__qualname__, - id(self)) - - def __init__(self): - super().__init__() - self._filter_order = [] - - def register(self, func, order): - """ - Add a function to the filter chain. - - :param func: A callable which is to be added to the filter chain. - :param order: An object indicating the ordering of the function - relative to the others. - :return: Token representing the registration. - - Register the function `func` as a filter into the chain. `order` must - be a value which is used as a sorting key to order the functions - registered in the chain. - - The type of `order` depends on the use of the filter, as does the - number of arguments and keyword arguments which `func` must accept. - This will generally be documented at the place where the - :class:`Filter` is used. - - Functions with the same order are sorted in the order of their - addition, with the function which was added earliest first. - - Remember that all values passed to `order` which are registered at the - same time in the same :class:`Filter` need to be totally orderable with - respect to each other. - - The returned token can be used to :meth:`unregister` a filter. - """ - token = self.Token() - self._filter_order.append((order, token, func)) - self._filter_order.sort(key=lambda x: x[0]) - return token - - def filter(self, obj, *args, **kwargs): - """ - Filter the given object through the filter chain. - - :param obj: The object to filter - :param args: Additional arguments to pass to each filter function. - :param kwargs: Additional keyword arguments to pass to each filter - function. - :return: The filtered object or :data:`None` - - See the documentation of :class:`Filter` on how filtering operates. - - Returns the object returned by the last function in the filter chain or - :data:`None` if any function returned :data:`None`. - """ - for _, _, func in self._filter_order: - obj = func(obj, *args, **kwargs) - if obj is None: - return None - return obj - - def unregister(self, token_to_remove): - """ - Unregister a filter function. - - :param token_to_remove: The token as returned by :meth:`register`. - - Unregister a function from the filter chain using the token returned by - :meth:`register`. - """ - for i, (_, token, _) in enumerate(self._filter_order): - if token == token_to_remove: - break - else: - raise ValueError("unregistered token: {!r}".format( - token_to_remove)) - del self._filter_order[i] - - @contextlib.contextmanager - def context_register(self, func, *args): - """ - :term:`Context manager ` which temporarily registers a - filter function. - - :param func: The filter function to register. - :param order: The sorting key for the filter function. - :rtype: :term:`context manager` - :return: Context manager which temporarily registers the filter - function. - - If :meth:`register` does not require `order` because it has been - overridden in a subclass, the `order` argument can be omitted here, - too. - - .. versionadded:: 0.9 - """ - token = self.register(func, *args) - try: - yield - finally: - self.unregister(token) - - -def first_signal(*signals): - """ - Connect to multiple signals and wait for the first to emit. - - :param signals: Signals to connect to. - :type signals: :class:`AdHocSignal` - :return: An awaitable for the first signal to emit. - - The awaitable returns the first argument passed to the signal. If the first - argument is an exception, the exception is re-raised from the awaitable. - - A common use-case is a situation where a class exposes a "on_finished" type - signal and an "on_failure" type signal. :func:`first_signal` can be used - to combine those nicely:: - - # e.g. a aioxmpp.im.conversation.AbstractConversation - conversation = ... - await first_signal( - # emits without arguments when the conversation is successfully - # entered - conversation.on_enter, - # emits with an exception when entering the conversation fails - conversation.on_failure, - ) - # await first_signal(...) will either raise an exception (failed) or - # return None (success) - - .. warning:: - - Only works with signals which emit with zero or one argument. Signals - which emit with more than one argument or with keyword arguments are - silently ignored! (Thus, if only such signals are connected, the - future will never complete.) - - (This is a side-effect of the implementation of - :meth:`AdHocSignal.AUTO_FUTURE`). - - .. note:: - - Does not work with coroutine signals (:class:`SyncAdHocSignal`). - """ - - fut = asyncio.Future() - for signal in signals: - signal.connect(fut, signal.AUTO_FUTURE) - return fut diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__init__.py deleted file mode 100644 index 1882523..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.carbons` -- Message Carbons (:xep:`280`) -####################################################### - -Message Carbons is an XMPP extension which allows an entity to receive copies -of inbound and outbound messages received and sent by other resources of the -same account. It is specified in :xep:`280`. The goal of this feature is to -allow users to have multiple devices which all have a consistent view on the -messages sent and received. - -This subpackage provides basic support for Message Carbons. It allows enabling -and disabling the feature at the server side. - -Service -======= - -.. currentmodule:: aioxmpp - -.. autoclass:: CarbonsClient - -.. currentmodule:: aioxmpp.carbons - - -.. currentmodule:: aioxmpp.carbons.xso -.. module:: aioxmpp.carbons.xso - -XSOs -==== - -.. attribute:: aioxmpp.Message.xep0280_sent - - On a Carbon message, this holds the :class:`~.carbons.xso.Sent` XSO which in - turn holds the carbonated stanza. - -.. attribute:: aioxmpp.Message.xep0280_received - - On a Carbon message, this holds the :class:`~.carbons.xso.Received` XSO - which in turn holds the carbonated stanza. - -.. autoclass:: Received - -.. autoclass:: Sent - -""" -from .service import CarbonsClient # NOQA: F401 diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 9ebf192..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/service.cpython-311.pyc deleted file mode 100644 index b321500..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index 565e36d..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/service.py deleted file mode 100644 index 8bc21d2..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/service.py +++ /dev/null @@ -1,106 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio - -import aioxmpp.service - -from aioxmpp.utils import namespaces - -from . import xso as carbons_xso - - -class CarbonsClient(aioxmpp.service.Service): - """ - Provide an interface to enable and disable Message Carbons on the server - side. - - .. note:: - - This service deliberately does not provide a way to actually obtain sent - or received carbonated messages. - - The common way for a service to do this would be a stanza filter (see - :class:`aioxmpp.stream.StanzaStream`); however, in general the use and - further distribution of carbonated messages highly depends on the - application: it does, for example, not make sense to simply unwrap - carbonated messages. - - .. automethod:: enable - - .. automethod:: disable - """ - - ORDER_AFTER = [ - aioxmpp.DiscoClient, - ] - - async def _check_for_feature(self): - disco_client = self.dependencies[aioxmpp.DiscoClient] - info = await disco_client.query_info( - self.client.local_jid.replace( - localpart=None, - resource=None, - ) - ) - - if namespaces.xep0280_carbons_2 not in info.features: - raise RuntimeError( - "Message Carbons ({}) are not supported by the server".format( - namespaces.xep0280_carbons_2 - ) - ) - - async def enable(self): - """ - Enable message carbons. - - :raises RuntimeError: if the server does not support message carbons. - :raises aioxmpp.XMPPError: if the server responded with an error to the - request. - :raises: as specified in :meth:`aioxmpp.Client.send` - """ - await self._check_for_feature() - - iq = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - payload=carbons_xso.Enable() - ) - - await self.client.send(iq) - - async def disable(self): - """ - Disable message carbons. - - :raises RuntimeError: if the server does not support message carbons. - :raises aioxmpp.XMPPError: if the server responded with an error to the - request. - :raises: as specified in :meth:`aioxmpp.Client.send` - """ - await self._check_for_feature() - - iq = aioxmpp.IQ( - type_=aioxmpp.IQType.SET, - payload=carbons_xso.Disable() - ) - - await self.client.send(iq) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/xso.py deleted file mode 100644 index 0200d55..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/carbons/xso.py +++ /dev/null @@ -1,105 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import aioxmpp.xso as xso - -from aioxmpp.utils import namespaces - -from ..misc import Forwarded -from ..stanza import Message, IQ - - -namespaces.xep0280_carbons_2 = "urn:xmpp:carbons:2" - - -@IQ.as_payload_class -class Enable(xso.XSO): - TAG = (namespaces.xep0280_carbons_2, "enable") - - -@IQ.as_payload_class -class Disable(xso.XSO): - TAG = (namespaces.xep0280_carbons_2, "disable") - - -class _CarbonsWrapper(xso.XSO): - forwarded = xso.Child([Forwarded]) - - @property - def stanza(self): - """ - The wrapped stanza, usually a :class:`aioxmpp.Message`. - - Internally, this accesses the :attr:`~.misc.Forwarded.stanza` attribute - of :attr:`forwarded`. If :attr:`forwarded` is :data:`None`, reading - this attribute returns :data:`None`. Writing to this attribute creates - a new :class:`~.misc.Forwarded` object if necessary, but re-uses an - existing object if available. - """ - if self.forwarded is None: - return None - return self.forwarded.stanza - - @stanza.setter - def stanza(self, value): - if self.forwarded is None: - self.forwarded = Forwarded() - self.forwarded.stanza = value - - -class Sent(_CarbonsWrapper): - """ - Wrap a stanza which was sent by another entity of the same account. - - :class:`Sent` XSOs are available in Carbon messages at - :attr:`aioxmpp.Message.xep0280_sent`. - - .. autoattribute:: stanza - - .. attribute:: forwarded - - The full :class:`~.misc.Forwarded` object which holds the sent stanza. - - """ - - TAG = (namespaces.xep0280_carbons_2, "sent") - - -class Received(_CarbonsWrapper): - """ - Wrap a stanza which was received by another entity of the same account. - - :class:`Received` XSOs are available in Carbon messages at - :attr:`aioxmpp.Message.xep0280_received`. - - .. autoattribute:: stanza - - .. attribute:: forwarded - - The full :class:`~.misc.Forwarded` object which holds the received - stanza. - - """ - TAG = (namespaces.xep0280_carbons_2, "received") - - -Message.xep0280_sent = xso.Child([Sent]) -Message.xep0280_received = xso.Child([Received]) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__init__.py deleted file mode 100644 index 616006a..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.chatstates` – Chat State Notification support (:xep:`0085`) -########################################################################## - -This module provides support to implement :xep:`Chat State -Notifications <85>`. - -XSOs -==== - -The module registers an attribute ``xep0085_chatstate`` with -:class:`aioxmpp.Message` to represent the chat state -notification tags, it takes values from the following enumeration (or -:data:`None` if no tag is present): - -.. autoclass:: ChatState - -Helpers -======= - -The module provides the following helper class, that handles the state -management for chat state notifications: - -.. autoclass:: ChatStateManager - -Its operation is controlled by one of the chat state strategies: - -.. autoclass:: DoNotEmit - -.. autoclass:: DiscoverSupport - -.. autoclass:: AlwaysEmit - -""" -from .xso import ChatState # NOQA: F401 -from .utils import (ChatStateManager, DoNotEmit, AlwaysEmit, # NOQA: F401 - DiscoverSupport) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 0c3649f..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/utils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/utils.cpython-311.pyc deleted file mode 100644 index 013b8ea..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/utils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index 7024a1d..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/utils.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/utils.py deleted file mode 100644 index d56ca29..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/utils.py +++ /dev/null @@ -1,152 +0,0 @@ -######################################################################## -# File name: utils.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -from abc import ABCMeta, abstractproperty - -from . import xso as chatstates_xso - - -class ChatStateStrategy(metaclass=ABCMeta): - - @abstractproperty - def sending(self): - """ - Return whether to send chat state notifications. - """ - raise NotImplementedError # pragma: no cover - - def reset(self): - """ - Reset the strategy (called after a reconnect). - """ - pass - - def no_reply(self): - """ - Called when the replies did not include a chat state. - """ - pass - - -class DoNotEmit(ChatStateStrategy): - """ - Chat state strategy: Do not emit chat state notifications. - """ - - @property - def sending(self): - return False - - -class DiscoverSupport(ChatStateStrategy): - """ - Chat state strategy: Discover support for chat state notifications - as per section 5.1 of :xep:`0085`. - """ - def __init__(self): - self.state = True - - def reset(self): - self.state = True - - def no_reply(self): - self.state = False - - @property - def sending(self): - return self.state - - -class AlwaysEmit(ChatStateStrategy): - """ - Chat state strategy: Always emit chat state notifications. - """ - - @property - def sending(self): - return True - - -class ChatStateManager: - """ - Manage the state of our chat state. - - :param strategy: the strategy used to decide whether to send - notifications (defaults to :class:`DiscoverSupport`) - :type strategy: a subclass of :class:`ChatStateStrategy` - - .. automethod:: handle - - Methods to pass in protocol level information: - - .. automethod:: no_reply - - .. automethod:: reset - """ - - def __init__(self, strategy=None): - self._state = chatstates_xso.ChatState.ACTIVE - if strategy is None: - strategy = DiscoverSupport() - self._strategy = strategy - - def handle(self, state, message=False): - """ - Handle a state update. - - :param state: the new chat state - :type state: :class:`~aioxmpp.chatstates.ChatState` - - :param message: pass true to indicate that we handle the - :data:`ACTIVE` state that is implied by - sending a content message. - :type message: :class:`bool` - - :returns: whether a standalone notification must be sent for - this state update, respective if a chat state - notification must be included with the message. - - :raises ValueError: if `message` is true and a state other - than :data:`ACTIVE` is passed. - """ - if message: - if state != chatstates_xso.ChatState.ACTIVE: - raise ValueError( - "Only the state ACTIVE can be sent with messages." - ) - elif self._state == state: - return False - - self._state = state - return self._strategy.sending - - def no_reply(self): - """ - Call this method if the peer did not include a chat state - notification. - """ - self._strategy.no_reply() - - def reset(self): - """ - Call this method on connection reset. - """ - self._strategy.reset() diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/xso.py deleted file mode 100644 index 07fcd34..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/chatstates/xso.py +++ /dev/null @@ -1,54 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import enum - -import aioxmpp.xso as xso -import aioxmpp.stanza as stanza - -from aioxmpp.utils import namespaces - - -namespaces.xep0085 = "http://jabber.org/protocol/chatstates" - - -class ChatState(enum.Enum): - """ - Enumeration of the chat states defined by :xep:`0085`: - - .. attribute:: ACTIVE - - .. attribute:: COMPOSING - - .. attribute:: PAUSED - - .. attribute:: INACTIVE - - .. attribute:: GONE - """ - ACTIVE = (namespaces.xep0085, "active") - COMPOSING = (namespaces.xep0085, "composing") - PAUSED = (namespaces.xep0085, "paused") - INACTIVE = (namespaces.xep0085, "inactive") - GONE = (namespaces.xep0085, "gone") - - -stanza.Message.xep0085_chatstate = xso.ChildTag(ChatState, allow_none=True) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/connector.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/connector.py deleted file mode 100644 index b982e1f..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/connector.py +++ /dev/null @@ -1,382 +0,0 @@ -######################################################################## -# File name: connector.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.connector` --- Ways to establish XML streams -########################################################### - -This module provides classes to establish XML streams. Currently, there are two -different ways to establish XML streams: normal TCP connection which is then -upgraded using STARTTLS, and directly using TLS. - -.. versionadded:: 0.6 - - The whole module was added in version 0.6. - -Abstract base class -=================== - -The connectors share a common abstract base class, :class:`BaseConnector`: - -.. autoclass:: BaseConnector - -Specific connectors -=================== - -.. autoclass:: STARTTLSConnector - -.. autoclass:: XMPPOverTLSConnector - -""" - -import abc -import asyncio -import logging - -from datetime import timedelta - -import aioxmpp.errors as errors -import aioxmpp.nonza as nonza -import aioxmpp.protocol as protocol -import aioxmpp.ssl_transport as ssl_transport - - -def to_ascii(s): - return s.encode("idna").decode("ascii") - - -class BaseConnector(metaclass=abc.ABCMeta): - """ - This is the base class for connectors. It defines the public interface of - all connectors. - - .. autoattribute:: tls_supported - - .. automethod:: connect - - Existing connectors: - - .. autosummary:: - - STARTTLSConnector - XMPPOverTLSConnector - - """ - - @abc.abstractproperty - def tls_supported(self): - """ - Boolean which indicates whether TLS is supported by this connector. - """ - - @abc.abstractproperty - def dane_supported(self): - """ - Boolean which indicates whether DANE is supported by this connector. - """ - - @abc.abstractmethod - async def connect(self, loop, metadata, domain, host, port, - negotiation_timeout, - base_logger=None): - """ - Establish a :class:`.protocol.XMLStream` for `domain` with the given - `host` at the given TCP `port`. - - `metadata` must be a :class:`.security_layer.SecurityLayer` instance to - use for the connection. `loop` must be a :class:`asyncio.BaseEventLoop` - to use. - - `negotiation_timeout` must be the maximum time in seconds to wait for - the server to reply in each negotiation step. The `negotiation_timeout` - is used as value for - :attr:`~aioxmpp.protocol.XMLStream.deadtime_hard_limit` in the returned - stream. - - Return a triple consisting of the :class:`asyncio.Transport`, the - :class:`.protocol.XMLStream` and the - :class:`aioxmpp.nonza.StreamFeatures` of the stream. - - To detect the use of TLS on the stream, check whether - :meth:`asyncio.Transport.get_extra_info` returns a non-:data:`None` - value for ``"ssl_object"``. - - `base_logger` is passed to :class:`aioxmpp.protocol.XMLStream`. - - .. versionchanged:: 0.10 - - Assignment of - :attr:`~aioxmpp.protocol.XMLStream.deadtime_hard_limit` was added. - """ - - -class STARTTLSConnector(BaseConnector): - """ - Establish an XML stream using STARTTLS. - - .. automethod:: connect - """ - - @property - def tls_supported(self): - return True - - @property - def dane_supported(self): - return False - - async def connect(self, loop, metadata, domain: str, host, port, - negotiation_timeout, base_logger=None): - """ - .. seealso:: - - :meth:`BaseConnector.connect` - For general information on the :meth:`connect` method. - - Connect to `host` at TCP port number `port`. The - :class:`aioxmpp.security_layer.SecurityLayer` object `metadata` is used - to determine the parameters of the TLS connection. - - First, a normal TCP connection is opened and the stream header is sent. - The stream features are waited for, and then STARTTLS is negotiated if - possible. - - :attr:`~.security_layer.SecurityLayer.tls_required` is honoured: if it - is true and TLS negotiation fails, :class:`~.errors.TLSUnavailable` is - raised. TLS negotiation is always attempted if - :attr:`~.security_layer.SecurityLayer.tls_required` is true, even if - the server does not advertise a STARTTLS stream feature. This might - help to prevent trivial downgrade attacks, and we don’t have anything - to lose at this point anymore anyways. - - :attr:`~.security_layer.SecurityLayer.ssl_context_factory` and - :attr:`~.security_layer.SecurityLayer.certificate_verifier_factory` are - used to configure the TLS connection. - - .. versionchanged:: 0.10 - - The `negotiation_timeout` is set as - :attr:`~.XMLStream.deadtime_hard_limit` on the returned XML stream. - """ - - features_future = asyncio.Future(loop=loop) - - stream = protocol.XMLStream( - to=domain, - features_future=features_future, - base_logger=base_logger, - ) - if base_logger is not None: - logger = base_logger.getChild(type(self).__name__) - else: - logger = logging.getLogger(".".join([ - __name__, type(self).__qualname__, - ])) - - try: - transport, _ = await ssl_transport.create_starttls_connection( - loop, - lambda: stream, - host=host, - port=port, - peer_hostname=host, - server_hostname=to_ascii(domain), - use_starttls=True, - ) - except: # NOQA - stream.abort() - raise - - stream.deadtime_hard_limit = timedelta(seconds=negotiation_timeout) - - features = await features_future - - try: - features[nonza.StartTLSFeature] - except KeyError: - if not metadata.tls_required: - return transport, stream, await features_future - logger.debug( - "attempting STARTTLS despite not announced since it is" - " required") - - try: - response = await protocol.send_and_wait_for( - stream, - [ - nonza.StartTLS(), - ], - [ - nonza.StartTLSFailure, - nonza.StartTLSProceed, - ] - ) - except errors.StreamError: - raise errors.TLSUnavailable( - "STARTTLS not supported by server, but required by client" - ) - - if not isinstance(response, nonza.StartTLSProceed): - if metadata.tls_required: - message = ( - "server failed to STARTTLS" - ) - - protocol.send_stream_error_and_close( - stream, - condition=errors.StreamErrorCondition.POLICY_VIOLATION, - text=message, - ) - - raise errors.TLSUnavailable(message) - return transport, stream, await features_future - - verifier = metadata.certificate_verifier_factory() - await verifier.pre_handshake( - domain, - host, - port, - metadata, - ) - - ssl_context = metadata.ssl_context_factory() - verifier.setup_context(ssl_context, transport) - - await stream.starttls( - ssl_context=ssl_context, - post_handshake_callback=verifier.post_handshake, - ) - - features = await protocol.reset_stream_and_get_features( - stream, - timeout=negotiation_timeout, - ) - - return transport, stream, features - - -class XMPPOverTLSConnector(BaseConnector): - """ - Establish an XML stream using XMPP-over-TLS, as per :xep:`368`. - - .. automethod:: connect - """ - - @property - def dane_supported(self): - return False - - @property - def tls_supported(self): - return True - - def _context_factory_factory(self, logger, metadata, verifier): - def context_factory(transport): - ssl_context = metadata.ssl_context_factory() - - if hasattr(ssl_context, "set_alpn_protos"): - try: - ssl_context.set_alpn_protos([b'xmpp-client']) - except NotImplementedError: - logger.warning( - "the underlying OpenSSL library does not support ALPN" - ) - else: - logger.warning( - "OpenSSL.SSL.Context lacks set_alpn_protos - " - "please update pyOpenSSL to a recent version" - ) - - verifier.setup_context(ssl_context, transport) - return ssl_context - return context_factory - - async def connect(self, loop, metadata, domain, host, port, - negotiation_timeout, base_logger=None): - """ - .. seealso:: - - :meth:`BaseConnector.connect` - For general information on the :meth:`connect` method. - - Connect to `host` at TCP port number `port`. The - :class:`aioxmpp.security_layer.SecurityLayer` object `metadata` is used - to determine the parameters of the TLS connection. - - The connector connects to the server by directly establishing TLS; no - XML stream is started before TLS negotiation, in accordance to - :xep:`368` and how legacy SSL was handled in the past. - - :attr:`~.security_layer.SecurityLayer.ssl_context_factory` and - :attr:`~.security_layer.SecurityLayer.certificate_verifier_factory` are - used to configure the TLS connection. - - .. versionchanged:: 0.10 - - The `negotiation_timeout` is set as - :attr:`~.XMLStream.deadtime_hard_limit` on the returned XML stream. - """ - - features_future = asyncio.Future(loop=loop) - - stream = protocol.XMLStream( - to=domain, - features_future=features_future, - base_logger=base_logger, - ) - - if base_logger is not None: - logger = base_logger.getChild(type(self).__name__) - else: - logger = logging.getLogger(".".join([ - __name__, type(self).__qualname__, - ])) - - verifier = metadata.certificate_verifier_factory() - await verifier.pre_handshake( - domain, - host, - port, - metadata, - ) - - context_factory = self._context_factory_factory(logger, metadata, - verifier) - - try: - transport, _ = await ssl_transport.create_starttls_connection( - loop, - lambda: stream, - host=host, - port=port, - peer_hostname=host, - server_hostname=to_ascii(domain), - post_handshake_callback=verifier.post_handshake, - ssl_context_factory=context_factory, - use_starttls=False, - ) - except: # NOQA - stream.abort() - raise - - stream.deadtime_hard_limit = timedelta(seconds=negotiation_timeout) - - return transport, stream, await features_future diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/custom_queue.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/custom_queue.py deleted file mode 100644 index 0263aa2..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/custom_queue.py +++ /dev/null @@ -1,76 +0,0 @@ -######################################################################## -# File name: custom_queue.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -import collections - - -class AsyncDeque: - def __init__(self, *, loop=None): - super().__init__() - self._loop = loop - self._data = collections.deque() - self._non_empty = asyncio.Event() - self._non_empty.clear() - - def __len__(self): - return len(self._data) - - def __contains__(self, obj): - return obj in self._data - - def empty(self): - return not self._non_empty.is_set() - - def put_nowait(self, obj): - self._data.append(obj) - self._non_empty.set() - - def putleft_nowait(self, obj): - self._data.appendleft(obj) - self._non_empty.set() - - def get_nowait(self): - try: - item = self._data.popleft() - except IndexError: - raise asyncio.QueueEmpty() from None - if not self._data: - self._non_empty.clear() - return item - - def getright_nowait(self): - try: - item = self._data.pop() - except IndexError: - raise asyncio.QueueEmpty() from None - if not self._data: - self._non_empty.clear() - return item - - async def get(self): - while not self._data: - await self._non_empty.wait() - return self.get_nowait() - - def clear(self): - self._data.clear() - self._non_empty.clear() diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__init__.py deleted file mode 100644 index 2afc264..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__init__.py +++ /dev/null @@ -1,119 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.disco` --- Service discovery support (:xep:`0030`) -################################################################# - -This module provides support for :xep:`Service Discovery <30>`. For this, it -provides a :class:`~aioxmpp.service.Service` subclass which can be loaded into -a client using :meth:`.Client.summon`. - -Services -======== - -The following services are provided by this subpackage and available directly -from :mod:`aioxmpp`: - -.. currentmodule:: aioxmpp - -.. autosummary:: - :nosignatures: - - DiscoServer - DiscoClient - -.. versionchanged:: 0.8 - - Prior to version 0.8, both services were provided by a single class - (:class:`aioxmpp.disco.Service`). This is not the case anymore, and there is - no replacement. - - If you need to write backwards compatible code, you could be doing something - like this:: - - try: - aioxmpp.DiscoServer - except AttributeError: - aioxmpp.DiscoServer = aioxmpp.disco.Service - aioxmpp.DiscoClient = aioxmpp.disco.Service - - This should work, because the old :class:`Service` class provided the - features of both of the individual classes. - -The detailed documentation of the classes follows: - -.. autoclass:: DiscoServer - -.. autoclass:: DiscoClient - -.. currentmodule:: aioxmpp.disco - -Entity information ------------------- - -.. autoclass:: Node - -.. autoclass:: StaticNode - -.. autoclass:: mount_as_node - -.. autoclass:: register_feature - -.. autoclass:: RegisteredFeature - -.. module:: aioxmpp.disco.xso - -.. currentmodule:: aioxmpp.disco.xso - -:mod:`.disco.xso` --- IQ payloads -================================= - -The submodule :mod:`aioxmpp.disco.xso` contains the :class:`~aioxmpp.xso.XSO` -classes which describe the IQ payloads used by this subpackage. - -You will encounter some of these in return values, but there should never be a -need to construct them by yourself; the :class:`~aioxmpp.disco.Service` handles -it all. - -Information queries -------------------- - -.. autoclass:: InfoQuery(*[, identities][, features][, node]) - -.. autoclass:: Feature(*[, var]) - -.. autoclass:: Identity(*[, category][, type_][, name][, lang]) - -Item queries ------------- - -.. autoclass:: ItemsQuery(*[, node][, items]) - -.. autoclass:: Item(*[, jid][, name][, node]) - -.. currentmodule:: aioxmpp.disco - -""" - -from . import xso # NOQA: F401 -from .service import (DiscoClient, DiscoServer, Node, StaticNode, # NOQA: F401 - mount_as_node, register_feature, RegisteredFeature) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 9061606..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/service.cpython-311.pyc deleted file mode 100644 index c1476af..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index e4f30fb..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/service.py deleted file mode 100644 index 2c6a554..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/service.py +++ /dev/null @@ -1,1037 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -import contextlib -import functools -import itertools - -import aioxmpp.cache -import aioxmpp.callbacks -import aioxmpp.errors as errors -import aioxmpp.service as service -import aioxmpp.structs as structs -import aioxmpp.stanza as stanza - -from aioxmpp.utils import namespaces - -from . import xso as disco_xso - - -class Node(object): - """ - A :class:`Node` holds the information related to a specific node within the - entity referred to by a JID, with respect to :xep:`30` semantics. - - A :class:`Node` always has at least one identity (or it will return - as ````). It may have zero or more features beyond the - :xep:`30` features which are statically included. - - To manage the identities and the features of a node, use the following - methods: - - .. automethod:: register_feature - - .. automethod:: unregister_feature - - .. automethod:: register_identity - - .. automethod:: set_identity_names - - .. automethod:: unregister_identity - - To access the declared features and identities, use: - - .. automethod:: iter_features - - .. automethod:: iter_identities - - .. automethod:: as_info_xso - - To access items, use: - - .. automethod:: iter_items - - Signals provide information about changes: - - .. signal:: on_info_changed() - - This signal emits when a feature or identity is registered or - unregistered. - - As mentioned, bare :class:`Node` objects have no items; there are - subclasses of :class:`Node` which support items: - - ====================== ================================================== - :class:`StaticNode` Support for a list of :class:`.xso.Item` instances - :class:`.DiscoServer` Support for "mountpoints" for node subtrees - ====================== ================================================== - - """ - STATIC_FEATURES = frozenset({namespaces.xep0030_info}) - - on_info_changed = aioxmpp.callbacks.Signal() - - def __init__(self): - super().__init__() - self._identities = {} - self._features = set() - - def iter_identities(self, stanza=None): - """ - Return an iterator of tuples describing the identities of the node. - - :param stanza: The IQ request stanza - :type stanza: :class:`~aioxmpp.IQ` or :data:`None` - :rtype: iterable of (:class:`str`, :class:`str`, :class:`str` or - :data:`None`, :class:`str` or :data:`None`) tuples - :return: :xep:`30` identities of this node - - `stanza` can be the :class:`aioxmpp.IQ` stanza of the request. This can - be used to hide a node depending on who is asking. If the returned - iterable is empty, the :class:`~.DiscoServer` returns an - ```` error. - - `stanza` may be :data:`None` if the identities are queried without - a specific request context. In that case, implementors should assume - that the result is visible to everybody. - - .. note:: - - Subclasses must allow :data:`None` for `stanza` and default it to - :data:`None`. - - Return an iterator which yields tuples consisting of the category, the - type, the language code and the name of each identity declared in this - :class:`Node`. - - Both the language code and the name may be :data:`None`, if no names or - a name without language code have been declared. - """ - for (category, type_), names in self._identities.items(): - for lang, name in names.items(): - yield category, type_, lang, name - if not names: - yield category, type_, None, None - - def iter_features(self, stanza=None): - """ - Return an iterator which yields the features of the node. - - :param stanza: The IQ request stanza - :type stanza: :class:`~aioxmpp.IQ` - :rtype: iterable of :class:`str` - :return: :xep:`30` features of this node - - `stanza` is the :class:`aioxmpp.IQ` stanza of the request. This can be - used to filter the list according to who is asking (not recommended). - - `stanza` may be :data:`None` if the features are queried without - a specific request context. In that case, implementors should assume - that the result is visible to everybody. - - .. note:: - - Subclasses must allow :data:`None` for `stanza` and default it to - :data:`None`. - - The features are returned as strings. The features demanded by - :xep:`30` are always returned. - - """ - return itertools.chain( - iter(self.STATIC_FEATURES), - iter(self._features) - ) - - def iter_items(self, stanza=None): - """ - Return an iterator which yields the items of the node. - - :param stanza: The IQ request stanza - :type stanza: :class:`~aioxmpp.IQ` - :rtype: iterable of :class:`~.disco.xso.Item` - :return: Items of the node - - `stanza` is the :class:`aioxmpp.IQ` stanza of the request. This can be - used to localize the list to the language of the stanza or filter it - according to who is asking. - - `stanza` may be :data:`None` if the items are queried without - a specific request context. In that case, implementors should assume - that the result is visible to everybody. - - .. note:: - - Subclasses must allow :data:`None` for `stanza` and default it to - :data:`None`. - - A bare :class:`Node` cannot hold any items and will thus return an - iterator which does not yield any element. - """ - return iter([]) - - def register_feature(self, var): - """ - Register a feature with the namespace variable `var`. - - If the feature is already registered or part of the default :xep:`30` - features, a :class:`ValueError` is raised. - """ - if var in self._features or var in self.STATIC_FEATURES: - raise ValueError("feature already claimed: {!r}".format(var)) - self._features.add(var) - self.on_info_changed() - - def register_identity(self, category, type_, *, names={}): - """ - Register an identity with the given `category` and `type_`. - - If there is already a registered identity with the same `category` and - `type_`, :class:`ValueError` is raised. - - `names` may be a mapping which maps :class:`.structs.LanguageTag` - instances to strings. This mapping will be used to produce - ```` declarations with the respective ``xml:lang`` and - ``name`` attributes. - """ - key = category, type_ - if key in self._identities: - raise ValueError("identity already claimed: {!r}".format(key)) - self._identities[key] = names - self.on_info_changed() - - def set_identity_names(self, category, type_, names={}): - """ - Update the names of an identity. - - :param category: The category of the identity to update. - :type category: :class:`str` - :param type_: The type of the identity to update. - :type type_: :class:`str` - :param names: The new internationalised names to set for the identity. - :type names: :class:`~.abc.Mapping` from - :class:`.structs.LanguageTag` to :class:`str` - :raises ValueError: if no identity with the given category and type - is currently registered. - """ - key = category, type_ - if key not in self._identities: - raise ValueError("identity not registered: {!r}".format(key)) - self._identities[key] = names - self.on_info_changed() - - def unregister_feature(self, var): - """ - Unregister a feature which has previously been registered using - :meth:`register_feature`. - - If the feature has not been registered previously, :class:`KeyError` is - raised. - - .. note:: - - The features which are mandatory per :xep:`30` are always registered - and cannot be unregistered. For the purpose of unregistration, they - behave as if they had never been registered; for the purpose of - registration, they behave as if they had been registered before. - - """ - self._features.remove(var) - self.on_info_changed() - - def unregister_identity(self, category, type_): - """ - Unregister an identity previously registered using - :meth:`register_identity`. - - If no identity with the given `category` and `type_` has been - registered before, :class:`KeyError` is raised. - - If the identity to remove is the last identity of the :class:`Node`, - :class:`ValueError` is raised; a node must always have at least one - identity. - """ - key = category, type_ - if key not in self._identities: - raise KeyError(key) - if len(self._identities) == 1: - raise ValueError("cannot remove last identity") - del self._identities[key] - self.on_info_changed() - - def as_info_xso(self, stanza=None): - """ - Construct a :class:`~.disco.xso.InfoQuery` response object for this - node. - - :param stanza: The IQ request stanza - :type stanza: :class:`~aioxmpp.IQ` - :rtype: iterable of :class:`~.disco.xso.InfoQuery` - :return: The disco#info response for this node. - - The resulting :class:`~.disco.xso.InfoQuery` carries the features and - identities as returned by :meth:`iter_features` and - :meth:`iter_identities`. The :attr:`~.disco.xso.InfoQuery.node` - attribute is at its default value and may need to be set by the caller - accordingly. - - `stanza` is passed to :meth:`iter_features` and - :meth:`iter_identities`. See those methods for information on the - effects. - - .. versionadded:: 0.9 - """ - - result = disco_xso.InfoQuery() - result.features.update(self.iter_features(stanza)) - result.identities[:] = ( - disco_xso.Identity( - category=category, - type_=type_, - lang=lang, - name=name, - ) - for category, type_, lang, name in self.iter_identities(stanza) - ) - return result - - -class StaticNode(Node): - """ - A :class:`StaticNode` is a :class:`Node` with a non-dynamic set of items. - - .. attribute:: items - - A list of :class:`.xso.Item` instances. These items will be returned - when the node is queried for it’s :xep:`30` items. - - It is the responsibility of the user to ensure that the set of items is - valid. This includes avoiding duplicate items. - - .. automethod:: clone - - """ - - def __init__(self): - super().__init__() - self.items = [] - - def iter_items(self, stanza=None): - return iter(self.items) - - @classmethod - def clone(cls, other_node): - """ - Clone another :class:`Node` and return as :class:`StaticNode`. - - :param other_node: The node which shall be cloned - :type other_node: :class:`Node` - :rtype: :class:`StaticNode` - :return: A static node which has the exact same features, identities - and items as `other_node`. - - The features and identities are copied over into the resulting - :class:`StaticNode`. The items of `other_node` are not copied but - merely referenced, so changes to the item *objects* of `other_node` - will be reflected in the result. - - .. versionadded:: 0.9 - """ - - result = cls() - result._features = { - feature for feature in other_node.iter_features() - if feature not in cls.STATIC_FEATURES - } - for category, type_, lang, name in other_node.iter_identities(): - names = result._identities.setdefault( - (category, type_), - aioxmpp.structs.LanguageMap() - ) - names[lang] = name - result.items = list(other_node.iter_items()) - return result - - -class DiscoServer(service.Service, Node): - """ - Answer Service Discovery (:xep:`30`) requests sent to this client. - - This service implements handlers for ``…disco#info`` and ``…disco#items`` - IQ requests. It provides methods to configure the contents of these - responses. - - .. seealso:: - - :class:`DiscoClient` - for a service which provides methods to query Service Discovery - information from other entities. - - The :class:`DiscoServer` inherits from :class:`~.disco.Node` to manage the - identities and features of the client. The identities and features declared - in the service using the :class:`~.disco.Node` interface on the - :class:`DiscoServer` instance are returned when a query is received for the - JID with an empty or unset ``node`` attribute. For completeness, the - relevant methods are listed here. Refer to the :class:`~.disco.Node` - documentation for details. - - .. autosummary:: - - .disco.Node.register_feature - .disco.Node.unregister_feature - .disco.Node.register_identity - .disco.Node.unregister_identity - - .. note:: - - Upon construction, the :class:`DiscoServer` adds a default identity with - category ``"client"`` and type ``"bot"`` to the root - :class:`~.disco.Node`. This is to comply with :xep:`30`, which specifies - that at least one identity must always be returned. Otherwise, the - service would be forced to send a malformed response or reply with - ````. - - After having added another identity, that default identity can be - removed. - - Other :class:`~.disco.Node` instances can be registered with the service - using the following methods: - - .. automethod:: mount_node - - .. automethod:: unmount_node - - """ - - on_info_result = aioxmpp.callbacks.Signal() - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - - self._node_mounts = { - None: self - } - - self.register_identity( - "client", "bot", - names={ - structs.LanguageTag.fromstr("en"): "aioxmpp default identity" - } - ) - - @aioxmpp.service.iq_handler( - aioxmpp.structs.IQType.GET, - disco_xso.InfoQuery) - async def handle_info_request(self, iq): - request = iq.payload - - try: - node = self._node_mounts[request.node] - except KeyError: - raise errors.XMPPModifyError( - condition=errors.ErrorCondition.ITEM_NOT_FOUND - ) - - response = node.as_info_xso(iq) - response.node = request.node - - if not response.identities: - raise errors.XMPPModifyError( - condition=errors.ErrorCondition.ITEM_NOT_FOUND, - ) - - return response - - @aioxmpp.service.iq_handler( - aioxmpp.structs.IQType.GET, - disco_xso.ItemsQuery) - async def handle_items_request(self, iq): - request = iq.payload - - try: - node = self._node_mounts[request.node] - except KeyError: - raise errors.XMPPModifyError( - condition=errors.ErrorCondition.ITEM_NOT_FOUND - ) - - response = disco_xso.ItemsQuery() - response.items.extend(node.iter_items(iq)) - - return response - - def mount_node(self, mountpoint, node): - """ - Mount the :class:`Node` `node` to be returned when a peer requests - :xep:`30` information for the node `mountpoint`. - """ - self._node_mounts[mountpoint] = node - - def unmount_node(self, mountpoint): - """ - Unmount the node mounted at `mountpoint`. - - .. seealso:: - - :meth:`mount_node` - for a way for mounting :class:`~.disco.Node` instances. - - """ - del self._node_mounts[mountpoint] - - -class DiscoClient(service.Service): - """ - Provide cache-backed Service Discovery (:xep:`30`) queries. - - This service provides methods to query Service Discovery information from - other entities in the XMPP network. The results are cached transparently. - - .. seealso:: - - :class:`.DiscoServer` - for a service which answers Service Discovery queries sent to the - client by other entities. - :class:`.EntityCapsService` - for a service which uses :xep:`115` to fill the cache of the - :class:`DiscoClient` with offline information. - - Querying other entities’ service discovery information: - - .. automethod:: query_info - - .. automethod:: query_items - - To prime the cache with information, the following methods can be used: - - .. automethod:: set_info_cache - - .. automethod:: set_info_future - - To control the size of caches, the following properties are available: - - .. autoattribute:: info_cache_size - :annotation: = 10000 - - .. autoattribute:: items_cache_size - :annotation: = 100 - - .. automethod:: flush_cache - - Usage example, assuming that you have a :class:`.node.Client` `client`:: - - import aioxmpp.disco as disco - # load service into node - sd = client.summon(aioxmpp.DiscoClient) - - # retrieve server information - server_info = yield from sd.query_info( - node.local_jid.replace(localpart=None, resource=None) - ) - - # retrieve resources - resources = yield from sd.query_items( - node.local_jid.bare() - ) - - """ - - on_info_result = aioxmpp.callbacks.Signal() - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - - self._info_pending = aioxmpp.cache.LRUDict() - self._info_pending.maxsize = 10000 - self._items_pending = aioxmpp.cache.LRUDict() - self._items_pending.maxsize = 100 - - self.client.on_stream_destroyed.connect( - self._clear_cache - ) - - @property - def info_cache_size(self): - """ - Maximum number of cache entries in the cache for :meth:`query_info`. - - This is mostly a measure to prevent malicious peers from exhausting - memory by spamming :mod:`aioxmpp.entitycaps` capability hashes. - - .. versionadded:: 0.9 - """ - return self._info_pending.maxsize - - @info_cache_size.setter - def info_cache_size(self, value): - self._info_pending.maxsize = value - - @property - def items_cache_size(self): - """ - Maximum number of cache entries in the cache for :meth:`query_items`. - - .. versionadded:: 0.9 - """ - return self._items_pending.maxsize - - @items_cache_size.setter - def items_cache_size(self, value): - self._items_pending.maxsize = value - - def _clear_cache(self): - for fut in self._info_pending.values(): - if not fut.done(): - fut.cancel() - self._info_pending.clear() - - for fut in self._items_pending.values(): - if not fut.done(): - fut.cancel() - self._items_pending.clear() - - def _handle_info_received(self, jid, node, task): - try: - result = task.result() - except Exception: - return - self.on_info_result(jid, node, result) - - def flush_cache(self): - """ - Clear the cache. - - This clears the internal cache in a way which lets existing queries - continue, but the next query for each target will behave as if - `require_fresh` had been set to true. - """ - self._info_pending.clear() - self._items_pending.clear() - - async def send_and_decode_info_query(self, jid, node): - request_iq = stanza.IQ(to=jid, type_=structs.IQType.GET) - request_iq.payload = disco_xso.InfoQuery(node=node) - - response = await self.client.send(request_iq) - - return response - - async def query_info(self, jid, *, - node=None, require_fresh=False, timeout=None, - no_cache=False): - """ - Query the features and identities of the specified entity. - - :param jid: The entity to query. - :type jid: :class:`aioxmpp.JID` - :param node: The node to query. - :type node: :class:`str` or :data:`None` - :param require_fresh: Boolean flag to discard previous caches. - :type require_fresh: :class:`bool` - :param timeout: Optional timeout for the response. - :type timeout: :class:`float` - :param no_cache: Boolean flag to forbid caching of the request. - :type no_cache: :class:`bool` - :rtype: :class:`.xso.InfoQuery` - :return: Service discovery information of the `node` at `jid`. - - The requests are cached. This means that only one request is ever fired - for a given target (identified by the `jid` and the `node`). The - request is re-used for all subsequent requests to that identity. - - If `require_fresh` is set to true, the above does not hold and a fresh - request is always created. The new request is the request which will be - used as alias for subsequent requests to the same identity. - - The visible effects of this are twofold: - - * Caching: Results of requests are implicitly cached - * Aliasing: Two concurrent requests will be aliased to one request to - save computing resources - - Both can be turned off by using `require_fresh`. In general, you should - not need to use `require_fresh`, as all requests are implicitly - cancelled whenever the underlying session gets destroyed. - - `no_cache` can be set to true to prevent future requests to be aliased - to this request, i.e. the request is not stored in the internal request - cache. This does not affect `require_fresh`, i.e. if a cached result is - available, it is used. - - The `timeout` can be used to restrict the time to wait for a - response. If the timeout triggers, :class:`TimeoutError` is raised. - - If :meth:`~.Client.send` raises an - exception, all queries which were running simultaneously for the same - target re-raise that exception. The result is not cached though. If a - new query is sent at a later point for the same target, a new query is - actually sent, independent of the value chosen for `require_fresh`. - - .. versionchanged:: 0.9 - - The `no_cache` argument was added. - """ - key = jid, node - - if not require_fresh: - try: - request = self._info_pending[key] - except KeyError: - pass - else: - try: - return await request - except asyncio.CancelledError: - pass - - request = asyncio.ensure_future( - self.send_and_decode_info_query(jid, node) - ) - request.add_done_callback( - functools.partial( - self._handle_info_received, - jid, - node - ) - ) - - if not no_cache: - self._info_pending[key] = request - try: - if timeout is not None: - try: - result = await asyncio.wait_for( - request, - timeout=timeout) - except asyncio.TimeoutError: - raise TimeoutError() - else: - result = await request - except: # NOQA - if request.done(): - try: - pending = self._info_pending[key] - except KeyError: - pass - else: - if pending is request: - del self._info_pending[key] - raise - - return result - - async def query_items(self, jid, *, - node=None, require_fresh=False, timeout=None): - """ - Query the items of the specified entity. - - :param jid: The entity to query. - :type jid: :class:`aioxmpp.JID` - :param node: The node to query. - :type node: :class:`str` or :data:`None` - :param require_fresh: Boolean flag to discard previous caches. - :type require_fresh: :class:`bool` - :param timeout: Optional timeout for the response. - :type timeout: :class:`float` - :rtype: :class:`.xso.ItemsQuery` - :return: Service discovery items of the `node` at `jid`. - - The arguments have the same semantics as with :meth:`query_info`, as - does the caching and error handling. - """ - key = jid, node - - if not require_fresh: - try: - request = self._items_pending[key] - except KeyError: - pass - else: - try: - return await request - except asyncio.CancelledError: - pass - - request_iq = stanza.IQ(to=jid, type_=structs.IQType.GET) - request_iq.payload = disco_xso.ItemsQuery(node=node) - - request = asyncio.ensure_future( - self.client.send(request_iq) - ) - - self._items_pending[key] = request - try: - if timeout is not None: - try: - result = await asyncio.wait_for( - request, - timeout=timeout) - except asyncio.TimeoutError: - raise TimeoutError() - else: - result = await request - except: # NOQA - if request.done(): - try: - pending = self._items_pending[key] - except KeyError: - pass - else: - if pending is request: - del self._items_pending[key] - raise - - return result - - def set_info_cache(self, jid, node, info): - """ - This is a wrapper around :meth:`set_info_future` which creates a future - and immediately assigns `info` as its result. - - .. versionadded:: 0.5 - """ - fut = asyncio.Future() - fut.set_result(info) - self.set_info_future(jid, node, fut) - - def set_info_future(self, jid, node, fut): - """ - Override the cache entry (if one exists) for :meth:`query_info` of the - `jid` and `node` combination with the given :class:`asyncio.Future` - fut. - - The future must receive a :class:`dict` compatible to the output of - :meth:`.xso.InfoQuery.to_dict`. - - As usual, the cache can be bypassed and cleared by passing - `require_fresh` to :meth:`query_info`. - - .. seealso:: - - Module :mod:`aioxmpp.entitycaps` - :xep:`0115` implementation which uses this method to prime the - cache with information derived from Entity Capability - announcements. - - .. note:: - - If a future is set to exception state, it will still remain and make - all queries for that target fail with that exception, until a query - uses `require_fresh`. - - .. versionadded:: 0.5 - """ - self._info_pending[jid, node] = fut - - -class mount_as_node(service.Descriptor): - """ - Service descriptor which mounts the :class:`~.service.Service` as - :class:`.DiscoServer` node. - - :param mountpoint: The mountpoint at which to mount the node. - :type mountpoint: :class:`str` - - .. versionadded:: 0.8 - - When the service is instaniated, it is mounted as :class:`~.disco.Node` at - the given `mountpoint`; it must thus also inherit from - :class:`~.disco.Node` or implement a compatible interface. - - .. autoattribute:: mountpoint - """ - - def __init__(self, mountpoint): - super().__init__() - self._mountpoint = mountpoint - - @property - def mountpoint(self): - """ - The mountpoint at which the node is mounted. - """ - return self._mountpoint - - @property - def required_dependencies(self): - return [DiscoServer] - - @contextlib.contextmanager - def init_cm(self, instance): - disco = instance.dependencies[DiscoServer] - disco.mount_node(self._mountpoint, instance) - try: - yield - finally: - disco.unmount_node(self._mountpoint) - - @property - def value_type(self): - return type(None) - - -class RegisteredFeature: - """ - Manage registration of a feature with a :class:`DiscoServer`. - - :param service: The service implementing the service discovery server. - :type service: :class:`DiscoServer` - :param feature: The feature to register. - :type feature: :class:`str` - - .. note:: - - Normally, you would not create an instance of this object manually. - Use the :class:`register_feature` descriptor on your - :class:`aioxmpp.Service` which will provide a - :class:`RegisteredFeature` object:: - - class Foo(aioxmpp.Service): - _some_feature = aioxmpp.disco.register_feature( - "urn:of:the:feature" - ) - - # after __init__, self._some_feature is a RegisteredFeature - # instance. - - @property - def some_feature_enabled(self): - # better do not expose the enabled boolean directly; this - # gives you the opportunity to do additional things when it - # is changed, such as disabling multiple features at once. - return self._some_feature.enabled - - @some_feature_enabled.setter - def some_feature_enabled(self, value): - self._some_feature.enabled = value - - .. versionadded:: 0.9 - - This object can be used as a context manager. Upon entering the context, - the feature is registered. When the context is left, the feature is - unregistered. - - .. note:: - - The context-manager use does not nest sensibly. Thus, do not use - th context-manager feature on :class:`RegisteredFeature` instances - which are created by :class:`register_feature`, as - :class:`register_feature` uses the context manager to - register/unregister the feature on initialisation/shutdown. - - Independently, it is possible to control the registration status of the - feature using :attr:`enabled`. - - .. autoattribute:: enabled - - .. autoattribute:: feature - - """ - - def __init__(self, service, feature): - self.__service = service - self.__feature = feature - self.__enabled = False - - @property - def enabled(self): - """ - Boolean indicating whether the feature is registered by this object - or not. - - When this attribute is changed to :data:`True`, the feature is - registered. When the attribute is changed to :data:`False`, the feature - is unregistered. - """ - return self.__enabled - - @enabled.setter - def enabled(self, value): - value = bool(value) - if value == self.__enabled: - return - - if value: - self.__service.register_feature(self.__feature) - else: - self.__service.unregister_feature(self.__feature) - - self.__enabled = value - - @property - def feature(self): - """ - The feature this object is controlling (read-only). - """ - return self.__feature - - def __enter__(self): - self.enabled = True - return self - - def __exit__(self, exc_type, exc_value, tb): - self.enabled = False - - -class register_feature(service.Descriptor): - """ - Service descriptor which registers a service discovery feature. - - :param feature: The feature to register. - :type feature: :class:`str` - - .. versionadded:: 0.8 - - When the service is instaniated, the `feature` is registered at the - :class:`~.DiscoServer`. - - On instances, the attribute which is described with this is a - :class:`RegisteredFeature` instance. - - .. versionchanged:: 0.9 - - :class:`RegisteredFeature` was added; before, the attribute reads as - :data:`None`. - """ - - def __init__(self, feature): - super().__init__() - self._feature = feature - - @property - def feature(self): - """ - The feature which is registered. - """ - return self._feature - - @property - def required_dependencies(self): - return [DiscoServer] - - def init_cm(self, instance): - disco = instance.dependencies[DiscoServer] - return RegisteredFeature(disco, self._feature) - - @property - def value_type(self): - return RegisteredFeature diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/xso.py deleted file mode 100644 index b5ee479..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/disco/xso.py +++ /dev/null @@ -1,307 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import aioxmpp.forms.xso as forms_xso -import aioxmpp.stanza as stanza -import aioxmpp.xso as xso - -from aioxmpp.utils import namespaces - -namespaces.xep0030_info = "http://jabber.org/protocol/disco#info" -namespaces.xep0030_items = "http://jabber.org/protocol/disco#items" - - -class Identity(xso.XSO): - """ - An identity declaration. The keyword arguments to the constructor can be - used to initialize attributes of the :class:`Identity` instance. - - .. attribute:: category - - The category of the identity. The value is not validated against the - values in the `registry - `_. - - .. attribute:: type_ - - The type of the identity. The value is not validated against the values - in the `registry - `_. - - .. attribute:: name - - The optional human-readable name of the identity. See also the - :attr:`lang` attribute. - - .. attribute:: lang - - The language of the :attr:`name`. This may be not :data:`None` even if - :attr:`name` is not set due to ``xml:lang`` propagation. - - """ - TAG = (namespaces.xep0030_info, "identity") - - category = xso.Attr(tag="category") - type_ = xso.Attr(tag="type") - name = xso.Attr(tag="name", default=None) - lang = xso.LangAttr() - - def __init__(self, *, - category="client", - type_="bot", - name=None, - lang=None): - super().__init__() - self.category = category - self.type_ = type_ - if name is not None: - self.name = name - if lang is not None: - self.lang = lang - - def __eq__(self, other): - try: - return (self.category == other.category and - self.type_ == other.type_ and - self.name == other.name and - self.lang == other.lang) - except AttributeError: - return NotImplemented - - def __repr__(self): - return "{}.{}(category={!r}, type_={!r}, name={!r}, lang={!r})".format( - self.__class__.__module__, - self.__class__.__qualname__, - self.category, - self.type_, - self.name, - self.lang) - - -class Feature(xso.XSO): - """ - A feature declaration. The keyword argument to the constructor can be used - to initialize the attribute of the :class:`Feature` instance. - - .. attribute:: var - - The namespace which identifies the feature. - - """ - - TAG = (namespaces.xep0030_info, "feature") - - var = xso.Attr(tag="var") - - def __init__(self, var): - super().__init__() - self.var = var - - -class FeatureSet(xso.AbstractElementType): - def get_xso_types(self): - return [Feature] - - def unpack(self, item): - return item.var - - def pack(self, var): - return Feature(var) - - -@stanza.IQ.as_payload_class -class InfoQuery(xso.CapturingXSO): - """ - A query for features and identities of an entity. The keyword arguments to - the constructor can be used to initialize the attributes. Note that - `identities` and `features` must be iterables of :class:`Identity` and - :class:`Feature`, respectively; these iterables are evaluated and the items - are stored in the respective attributes. - - .. attribute:: node - - The node at which the query is directed. - - .. attribute:: identities - - The identities of the entity, as :class:`Identity` instances. Each - entity has at least one identity. - - .. attribute:: features - - The features of the entity, as a set of strings. Each string represents - a :class:`Feature` instance with the corresponding :attr:`~.Feature.var` - attribute. - - .. attribute:: captured_events - - If the object was created by parsing an XML stream, this attribute holds - a list of events which were used when parsing it. - - Otherwise, this is :data:`None`. - - .. versionadded:: 0.5 - - .. automethod:: to_dict - - """ - __slots__ = ("captured_events",) - - TAG = (namespaces.xep0030_info, "query") - - node = xso.Attr(tag="node", default=None) - - identities = xso.ChildList([Identity]) - - features = xso.ChildValueList( - FeatureSet(), - container_type=set - ) - - exts = xso.ChildList([forms_xso.Data]) - - def __init__(self, *, identities=(), features=(), node=None): - super().__init__() - self.captured_events = None - self.identities.extend(identities) - self.features.update(features) - if node is not None: - self.node = node - - def to_dict(self): - """ - Convert the query result to a normalized JSON-like - representation. - - The format is a subset of the format used by the `capsdb`__. Obviously, - the node name and hash type are not included; otherwise, the format is - identical. - - __ https://github.com/xnyhps/capsdb - """ - identities = [] - for identity in self.identities: - identity_dict = { - "category": identity.category, - "type": identity.type_, - } - if identity.lang is not None: - identity_dict["lang"] = identity.lang.match_str - if identity.name is not None: - identity_dict["name"] = identity.name - identities.append(identity_dict) - - features = sorted(self.features) - - forms = [] - for form in self.exts: - forms.append({ - field.var: list(field.values) - for field in form.fields - if field.var is not None - }) - - result = { - "identities": identities, - "features": features, - "forms": forms - } - - return result - - def _set_captured_events(self, events): - self.captured_events = events - - -class Item(xso.XSO): - """ - An item declaration. The keyword arguments to the constructor can be used - to initialize the attributes of the :class:`Item` instance. - - .. attribute:: jid - - :class:`~aioxmpp.JID` of the entity represented by the item. - - .. attribute:: node - - Node of the item - - .. attribute:: name - - Name of the item - - """ - - TAG = (namespaces.xep0030_items, "item") - UNKNOWN_CHILD_POLICY = xso.UnknownChildPolicy.DROP - - jid = xso.Attr( - tag="jid", - type_=xso.JID(), - # FIXME: validator for full jid - ) - - name = xso.Attr( - tag="name", - default=None, - ) - - node = xso.Attr( - tag="node", - default=None, - ) - - def __init__(self, jid, name=None, node=None): - super().__init__() - self.jid = jid - self.name = name - self.node = node - - -@stanza.IQ.as_payload_class -class ItemsQuery(xso.XSO): - """ - A query for items at a specific entity. The keyword arguments to the - constructor can be used to initialize the attributes of the - :class:`ItemsQuery`. Note that `items` must be an iterable of :class:`Item` - instances. The iterable will be evaluated and the items will be stored in - the :attr:`items` attribute. - - .. attribute:: node - - Node at which the query is directed - - .. attribute:: items - - The items at the addressed entity. - - """ - TAG = (namespaces.xep0030_items, "query") - - node = xso.Attr(tag="node", default=None) - - items = xso.ChildList([Item]) - - def __init__(self, *, node=None, items=()): - super().__init__() - self.items.extend(items) - if node is not None: - self.node = node diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/dispatcher.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/dispatcher.py deleted file mode 100644 index 012d674..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/dispatcher.py +++ /dev/null @@ -1,451 +0,0 @@ -######################################################################## -# File name: dispatcher.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.dispatcher` --- Dispatch stanzas to callbacks -############################################################ - -.. versionadded:: 0.9 - - The whole module was added in 0.9. - -Stanza Dispatchers for Messages and Presences -============================================= - -.. autoclass:: SimpleMessageDispatcher - -.. autoclass:: SimplePresenceDispatcher - - -Decorators for :class:`aioxmpp.service.Service` Methods -======================================================= - -.. autodecorator:: message_handler - -.. autodecorator:: presence_handler - -Test Functions --------------- - -.. autofunction:: is_message_handler - -.. autofunction:: is_presence_handler - -Base Class for Stanza Dispatchers -================================= - -.. autoclass:: SimpleStanzaDispatcher -""" -import abc -import asyncio -import contextlib - -import aioxmpp.service -import aioxmpp.stream - - -class SimpleStanzaDispatcher(metaclass=abc.ABCMeta): - """ - Dispatch stanzas based on their sender and type. - - This is a service base class (not a service you should summon) which can be - used to implement simple, pre-0.9 presence and message dispatching. - - For users, the following methods are relevant: - - .. automethod:: register_callback - - .. automethod:: unregister_callback - - .. automethod:: handler_context - - For deriving classes, the following methods are relevant: - - .. automethod:: _feed - - Subclasses must also provide the following property: - - .. autoattribute:: local_jid - - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._map = {} - - @abc.abstractproperty - def local_jid(self): - """ - The bare JID of the client for which this dispatcher is used. - - This is required to map missing ``@from`` attributes to this JID. The - attribute must be provided by implementing subclasses. - """ - - def _feed(self, stanza): - """ - Dispatch the given `stanza`. - - :param stanza: Stanza to dispatch - :type stanza: :class:`~.StanzaBase` - :rtype: :class:`bool` - :return: true if the stanza was dispatched, false otherwise. - - Dispatch the stanza to up to one handler registered on the dispatcher. - If no handler is found for the stanza, :data:`False` is returned. - Otherwise, :data:`True` is returned. - """ - from_ = stanza.from_ - if from_ is None: - from_ = self.local_jid - - keys = [ - (stanza.type_, from_, False), - (stanza.type_, from_.bare(), True), - (None, from_, False), - (None, from_.bare(), True), - (stanza.type_, None, False), - (None, from_, False), - (None, None, False), - ] - - for key in keys: - try: - cb = self._map[key] - except KeyError: - continue - cb(stanza) - return - - def register_callback(self, type_, from_, cb, *, - wildcard_resource=True): - """ - Register a callback function. - - :param type_: Stanza type to listen for, or :data:`None` for a - wildcard match. - :param from_: Sender to listen for, or :data:`None` for a full wildcard - match. - :type from_: :class:`aioxmpp.JID` or :data:`None` - :param cb: Callback function to register - :param wildcard_resource: Whether to wildcard the resourcepart of the - JID. - :type wildcard_resource: :class:`bool` - :raises ValueError: if another function is already registered for the - callback slot. - - `cb` will be called whenever a stanza with the matching `type_` and - `from_` is processed. The following wildcarding rules apply: - - 1. If the :attr:`~aioxmpp.stanza.StanzaBase.from_` attribute of the - stanza has a resourcepart, the following lookup order for callbacks is used: - - +---------------------------+----------------------------------+----------------------+ - |``type_`` |``from_`` |``wildcard_resource`` | - +===========================+==================================+======================+ - |:attr:`~.StanzaBase.type_` |:attr:`~.StanzaBase.from_` |*any* | - +---------------------------+----------------------------------+----------------------+ - |:attr:`~.StanzaBase.type_` |*bare* :attr:`~.StanzaBase.from_` |:data:`True` | - +---------------------------+----------------------------------+----------------------+ - |:data:`None` |:attr:`~.StanzaBase.from_` |*any* | - +---------------------------+----------------------------------+----------------------+ - |:data:`None` |*bare* :attr:`~.StanzaBase.from_` |:data:`True` | - +---------------------------+----------------------------------+----------------------+ - |:attr:`~.StanzaBase.type_` |:data:`None` |*any* | - +---------------------------+----------------------------------+----------------------+ - |:data:`None` |:data:`None` |*any* | - +---------------------------+----------------------------------+----------------------+ - - 2. If the :attr:`~aioxmpp.stanza.StanzaBase.from_` attribute of the - stanza does *not* have a resourcepart, the following lookup order - for callbacks is used: - - +---------------------------+---------------------------+----------------------+ - |``type_`` |``from_`` |``wildcard_resource`` | - +===========================+===========================+======================+ - |:attr:`~.StanzaBase.type_` |:attr:`~.StanzaBase.from_` |:data:`False` | - +---------------------------+---------------------------+----------------------+ - |:data:`None` |:attr:`~.StanzaBase.from_` |:data:`False` | - +---------------------------+---------------------------+----------------------+ - |:attr:`~.StanzaBase.type_` |:data:`None` |*any* | - +---------------------------+---------------------------+----------------------+ - |:data:`None` |:data:`None` |*any* | - +---------------------------+---------------------------+----------------------+ - - Only the first callback which matches is called. `wildcard_resource` is - ignored if `from_` is a full JID or :data:`None`. - - .. note:: - - When the server sends a stanza without from attribute, it is - replaced with the bare :attr:`local_jid`, as per :rfc:`6120`. - - """ # NOQA: E501 - if from_ is None or not from_.is_bare: - wildcard_resource = False - - key = (type_, from_, wildcard_resource) - if key in self._map: - raise ValueError( - "only one listener allowed per matcher" - ) - - self._map[type_, from_, wildcard_resource] = cb - - def unregister_callback(self, type_, from_, *, - wildcard_resource=True): - """ - Unregister a callback function. - - :param type_: Stanza type to listen for, or :data:`None` for a - wildcard match. - :param from_: Sender to listen for, or :data:`None` for a full wildcard - match. - :type from_: :class:`aioxmpp.JID` or :data:`None` - :param wildcard_resource: Whether to wildcard the resourcepart of the - JID. - :type wildcard_resource: :class:`bool` - - The callback must be disconnected with the same arguments as were used - to connect it. - """ - if from_ is None or not from_.is_bare: - wildcard_resource = False - - self._map.pop((type_, from_, wildcard_resource)) - - @contextlib.contextmanager - def handler_context(self, type_, from_, cb, *, wildcard_resource=True): - """ - Context manager which temporarily registers a callback. - - The arguments are the same as for :meth:`register_callback`. - - When the context is entered, the callback `cb` is registered. When the - context is exited, no matter if an exception is raised or not, the - callback is unregistered. - """ - self.register_callback( - type_, from_, cb, - wildcard_resource=wildcard_resource - ) - try: - yield - finally: - self.unregister_callback( - type_, from_, - wildcard_resource=wildcard_resource - ) - - -class SimpleMessageDispatcher(aioxmpp.service.Service, - SimpleStanzaDispatcher): - """ - Dispatch messages to callbacks. - - This :class:`~aioxmpp.service.Service` dispatches :class:`~aioxmpp.Message` - stanzas to callbacks. Callbacks registrations are managed with the - :meth:`.SimpleStanzaDispatcher.register_callback` and - :meth:`.SimpleStanzaDispatcher.unregister_callback` methods of the base - class. The `type_` argument to these methods must be a - :class:`aioxmpp.MessageType` or :data:`None` to make any sense. - - .. note:: - - It is not recommended to mix the use of a - :class:`SimpleMessageDispatcher` with the modern Instant Messaging - features provided by the :mod:`aioxmpp.im` module. Both will receive the - messages and this may thus lead to duplicate messages. - - """ - - @property - def local_jid(self): - return self.client.local_jid - - @aioxmpp.service.depsignal(aioxmpp.stream.StanzaStream, - "on_message_received") - def _feed(self, stanza): - super()._feed(stanza) - - -class SimplePresenceDispatcher(aioxmpp.service.Service, - SimpleStanzaDispatcher): - """ - Dispatch presences to callbacks. - - This :class:`~aioxmpp.service.Service` dispatches - :class:`~aioxmpp.Presence` stanzas to callbacks. Callbacks registrations - are managed with the :meth:`.SimpleStanzaDispatcher.register_callback` and - :meth:`.SimpleStanzaDispatcher.unregister_callback` methods of the base - class. The `type_` argument to these methods must be a - :class:`aioxmpp.MessageType` or :data:`None` to make any sense. - - .. warning:: - - It is not recommended to mix the use of a - :class:`SimplePresenceDispatcher` with :class:`aioxmpp.RosterClient` and - :class:`aioxmpp.PresenceClient`. Both of these register callbacks at the - :class:`SimplePresenceDispatcher`. Registering callbacks for different - slots will either make those callbacks not be called at all or will - make the services miss stanzas. - """ - - @property - def local_jid(self): - return self.client.local_jid - - @aioxmpp.service.depsignal(aioxmpp.stream.StanzaStream, - "on_presence_received") - def _feed(self, stanza): - super()._feed(stanza) - - -def _apply_message_handler(instance, stream, func, type_, from_): - return instance.dependencies[SimpleMessageDispatcher].handler_context( - type_, - from_, - func, - ) - - -def _apply_presence_handler(instance, stream, func, type_, from_): - return instance.dependencies[SimplePresenceDispatcher].handler_context( - type_, - from_, - func, - ) - - -def message_handler(type_, from_): - """ - Register the decorated function as message handler. - - :param type_: Message type to listen for - :type type_: :class:`~.MessageType` - :param from_: Sender JIDs to listen for - :type from_: :class:`aioxmpp.JID` or :data:`None` - :raise TypeError: if the decorated object is a coroutine function - - .. seealso:: - - :meth:`~.StanzaStream.register_message_callback` - for more details on the `type_` and `from_` arguments - - .. versionchanged:: 0.9 - - This is now based on - :class:`aioxmpp.dispatcher.SimpleMessageDispatcher`. - """ - - def decorator(f): - if asyncio.iscoroutinefunction(f): - raise TypeError("message_handler must not be a coroutine function") - - aioxmpp.service.add_handler_spec( - f, - aioxmpp.service.HandlerSpec( - (_apply_message_handler, (type_, from_)), - require_deps=( - SimpleMessageDispatcher, - ) - ) - ) - return f - return decorator - - -def presence_handler(type_, from_): - """ - Register the decorated function as presence stanza handler. - - :param type_: Presence type to listen for - :type type_: :class:`~.PresenceType` - :param from_: Sender JIDs to listen for - :type from_: :class:`aioxmpp.JID` or :data:`None` - :raise TypeError: if the decorated object is a coroutine function - - .. seealso:: - - :meth:`~.StanzaStream.register_presence_callback` - for more details on the `type_` and `from_` arguments - - .. versionchanged:: 0.9 - - This is now based on - :class:`aioxmpp.dispatcher.SimplePresenceDispatcher`. - """ - - def decorator(f): - if asyncio.iscoroutinefunction(f): - raise TypeError( - "presence_handler must not be a coroutine function" - ) - - aioxmpp.service.add_handler_spec( - f, - aioxmpp.service.HandlerSpec( - (_apply_presence_handler, (type_, from_)), - require_deps=( - SimplePresenceDispatcher, - ) - ) - ) - return f - return decorator - - -def is_message_handler(type_, from_, cb): - """ - Return true if `cb` has been decorated with :func:`message_handler` for the - given `type_` and `from_`. - """ - - try: - handlers = aioxmpp.service.get_magic_attr(cb) - except AttributeError: - return False - - return aioxmpp.service.HandlerSpec( - (_apply_message_handler, (type_, from_)), - require_deps=( - SimpleMessageDispatcher, - ) - ) in handlers - - -def is_presence_handler(type_, from_, cb): - """ - Return true if `cb` has been decorated with :func:`presence_handler` for - the given `type_` and `from_`. - """ - - try: - handlers = aioxmpp.service.get_magic_attr(cb) - except AttributeError: - return False - - return aioxmpp.service.HandlerSpec( - (_apply_presence_handler, (type_, from_)), - require_deps=( - SimplePresenceDispatcher, - ) - ) in handlers diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__init__.py deleted file mode 100644 index ad33ae8..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__init__.py +++ /dev/null @@ -1,499 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.e2etest` --- Framework for writing integration tests for :mod:`aioxmpp` -###################################################################################### - -This subpackage provides utilities for writing end-to-end or intgeration tests -for :mod:`aioxmpp` components. - -.. warning:: - - For now, the API of this subpackage is classified as internal. Please do not - test your external components using this API, as it is experimental and - subject to change. - -Overview -======== - -The basic concept is that tests are written like normal unittests. However, -tests are written by inheriting classes from :class:`aioxmpp.e2etest.TestCase` -instead of :mod:`unittest.TestCase`. :class:`.e2etest.TestCase` has the -:attr:`~.e2etest.TestCase.provisioner` attribute which provides access to a -:class:`.provision.Provisioner` instance. - -Provisioners are objects which provide a way to obtain a connected XMPP client. -The JID to which the client is bound is unspecified; however, each client gets -a unique bare JID and the clients are able to communicate with each other. In -addition, provisioners provide information about the environment in which the -clients act. This includes providing JIDs of entities implementing specific -protocols or features. The details are explained in the documentation of the -:class:`~.provision.Provisioner` base class. - -By default, tests which are written with :class:`.e2etest.TestCase` are skipped -when using the normal test runners. This is because the provisioners need to be -configured; this is handled using a custom nosetests plugin which is not loaded -by default (for good reasons). To run the tests, use (instead of the normal -``nosetests3`` binary): - -.. code-block:: console - - $ python3 -m aioxmpp.e2etest - -The command line interface is identical to the one of ``nosetests3``, except -that additional options are provided to configure the plugin. In fact, -:mod:`aioxmpp.e2etest` is simply a nose test runner with an additional plugin. - -By default, the configuration is read from ``./.local/e2etest.ini``. For -details on configuring the provisioners, see :ref:`the developer guide -`. - -Main API -======== - -Decorators for test methods ---------------------------- - -The following decorators can be used on test methods (including ``setUp`` and -``tearDown``): - -.. autodecorator:: require_feature - -.. autodecorator:: require_identity - -.. autodecorator:: require_feature_subset - -.. autodecorator:: skip_with_quirk - -General decorators ------------------- - -.. autodecorator:: blocking() - -.. autodecorator:: blocking_timed() - -.. autodecorator:: blocking_with_timeout - -Class for test cases --------------------- - -.. autoclass:: TestCase - -.. currentmodule:: aioxmpp.e2etest.provision - -Provisioners -============ - -.. autoclass:: Provisioner - -.. autoclass:: AnonymousProvisioner() - -.. autoclass:: AnyProvisioner() - -.. autoclass:: StaticPasswordProvisioner() - -.. currentmodule:: aioxmpp.e2etest - -.. autoclass:: Quirk - -.. currentmodule:: aioxmpp.e2etest.provision - -Helper functions ----------------- - -.. autofunction:: discover_server_features - -.. autofunction:: configure_tls_config - -.. autofunction:: configure_quirks -""" # NOQA: E501 -import asyncio -import configparser -import functools -import importlib -import logging -import os -import unittest - -import pytest - -from ..testutils import get_timeout -from .utils import blocking -from .provision import Quirk # NOQA: F401 - - -provisioner = None -config = None -only_e2etest = False -e2etest_record = None -timeout = get_timeout(1.0) - - -def require_feature(feature_var, argname=None, *, multiple=False): - """ - :param feature_var: :xep:`30` feature ``var`` of the required feature - :type feature_var: :class:`str` - :param argname: Optional argument name to pass the :class:`FeatureInfo` to - :type argname: :class:`str` or :data:`None` - :param multiple: If true, all peers are returned instead of a random one. - :type multiple: :class:`bool` - - Before running the function, it is tested that the feature specified by - `feature_var` is provided in the environment of the current provisioner. If - it is not, :class:`unittest.SkipTest` is raised to skip the test. - - If the feature is available, the :class:`FeatureInfo` instance is passed to - the decorated function. If `argname` is :data:`None`, the feature info is - passed as additional positional argument. otherwise, it is passed as - keyword argument using the `argname`. - - If `multiple` is true, all peers supporting the given feature are passed - in a set. Otherwise, only a random peer is returned. - - This decorator can be used on test methods, but not on test classes. If you - want to skip all tests in a class, apply the decorator to the ``setUp`` - method. - """ - if isinstance(feature_var, str): - feature_var = [feature_var] - - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - global provisioner - if multiple: - arg = provisioner.get_feature_providers(feature_var) - has_provider = bool(arg) - else: - arg = provisioner.get_feature_provider(feature_var) - has_provider = arg is not None - if not has_provider: - raise unittest.SkipTest( - "provisioner does not provide a peer with " - "{!r}".format(feature_var) - ) - - if argname is None: - args = args+(arg,) - else: - kwargs[argname] = arg - - return f(*args, **kwargs) - return wrapper - - return decorator - - -def require_identity(category, type_, argname=None): - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - global provisioner - arg = provisioner.get_identity_provider(category, type_) - has_provider = arg is not None - if not has_provider: - raise unittest.SkipTest( - "provisioner does not provide a peer with a " - "{!r} identity".format((category, type_)) - ) - - if argname is None: - args = args+(arg,) - else: - kwargs[argname] = arg - - return f(*args, **kwargs) - return wrapper - - return decorator - - -def require_feature_subset(feature_vars, required_subset=[]): - required_subset = set(required_subset) - feature_vars = set(feature_vars) | required_subset - - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - global provisioner - jid, subset = provisioner.get_feature_subset_provider( - feature_vars, - required_subset - ) - if jid is None: - raise unittest.SkipTest( - "no peer could provide a subset of {!r} with at least " - "{!r}".format( - feature_vars, - required_subset, - ) - ) - - return f(*(args+(jid, feature_vars)), - **kwargs) - return wrapper - - return decorator - - -def require_pep(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - global provisioner - if not provisioner.has_pep(): - raise unittest.SkipTest( - "the provisioned account does not support PEP", - ) - - return f(*args, **kwargs) - return wrapper - - -def skip_with_quirk(quirk): - """ - :param quirk: The quirk to skip on - :type quirk: :class:`Quirks` - - If the provisioner indicates that the environment has the given `quirk`, - the test is skipped. - - This decorator can be used on test methods, but not on test classes. If you - want to skip all tests in a class, apply the decorator to the ``setUp`` - method. - """ - - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - global provisioner - if provisioner.has_quirk(quirk): - raise unittest.SkipTest( - "provisioner has quirk {!r}".format(quirk) - ) - return f(*args, **kwargs) - return wrapper - - return decorator - - -def blocking_with_timeout(timeout): - """ - The decorated coroutine function is run using the - :meth:`~asyncio.AbstractEventLoop.run_until_complete` method of the current - (at the time of call) event loop. - - If the execution takes longer than `timeout` seconds, - :class:`asyncio.TimeoutError` is raised. - - The decorated function behaves like a normal function and is not a - coroutine function. - - This decorator must be applied to a coroutine function (or method). - """ - - def decorator(f): - @blocking - @functools.wraps(f) - async def wrapper(*args, **kwargs): - return await asyncio.wait_for(f(*args, **kwargs), timeout) - return wrapper - return decorator - - -def blocking_timed(f): - """ - Like :func:`blocking_with_timeout`, the decorated coroutine function is - executed using :meth:`asyncio.AbstractEventLoop.run_until_complete` with a - timeout, but the timeout is configured in the end-to-end test configuration - (see :ref:`dg-end-to-end-tests`). - - This is the recommended decorator for any test function or method, to - prevent the tests from hanging when anythin goes wrong. The timeout is - under control of the provisioner configuration, which means that it can be - adapted to different setups (for example, running against an XMPP server in - the internet will be slower than if it runs on localhost). - - The decorated function behaves like a normal function and is not a - coroutine function. - - This decorator must be applied to a coroutine function (or method). - """ - @blocking - @functools.wraps(f) - async def wrapper(*args, **kwargs): - global timeout - await asyncio.wait_for(f(*args, **kwargs), timeout) - return wrapper - - -@blocking -async def setup_package(): - global provisioner, config, timeout - if config is None: - return - - timeout = config.getfloat("global", "timeout", fallback=timeout) - - provisioner_name = config.get("global", "provisioner") - module_path, class_name = provisioner_name.rsplit(".", 1) - mod = importlib.import_module(module_path) - cls_ = getattr(mod, class_name) - - section = config[provisioner_name] - provisioner = cls_() - provisioner.configure(section) - await provisioner.initialise() - - -def teardown_package(): - global provisioner, config - if config is None: - return - - loop = asyncio.get_event_loop() - loop.run_until_complete(provisioner.finalise()) - loop.close() - - -class TestCase(unittest.TestCase): - """ - A subclass of :class:`unittest.TestCase` for end-to-end test cases. - - This subclass provides a single additional attribute: - - .. autoattribute:: provisioner - """ - - __unittest_skip__ = True - __unittest_skip_why__ = "this is not the aioxmpp test runner" - - @property - def provisioner(self): - """ - This is the configured :class:`.provision.Provisioner` instance. - - If no provisioner is configured (for example because the e2etest nose - plugin is not loaded), this reads as :data:`None`. - - .. note:: - - Under nosetests and the vanilla unittest runner, tests inheriting - from :class:`TestCase` are automatically skipped if - :attr:`provisioner` is :data:`None`. - """ - global provisioner - return provisioner - - -def pytest_load_initial_conftests(early_config, parser, args): - parser.addoption( - "--e2etest-config", - dest="aioxmpp_e2e_config", - default=".local/e2etest.ini", - metavar="FILE", - help="Configuration file for end-to-end tests " - "(default: .local/e2etest.ini)", - ) - parser.addoption( - "--e2etest-record", - dest="aioxmpp_e2e_record", - metavar="FILE", - default=None, - help="A file to write a transcript to" - ) - parser.addoption( - "--e2etest-only", - dest="aioxmpp_e2e_only", - action="store_true", - default=False, - help="If set, only E2E tests will be executed." - ) - - -def pytest_configure(config): - config.addinivalue_line("markers", "aioxmpp_e2etest: end-to-end test") - - -def pytest_cmdline_main(config): - return _pytest_cmdline_main_impl(config) - - -def _pytest_cmdline_main_impl(pytest_config): - global config, only_e2etest, e2etest_record - config = configparser.ConfigParser() - with open(pytest_config.option.aioxmpp_e2e_config, "r") as f: - config.read_file(f) - - e2etest_record = pytest_config.option.aioxmpp_e2e_record - only_e2etest = pytest_config.option.aioxmpp_e2e_only - TestCase.__unittest_skip__ = False - - -def pytest_sessionstart(session): - setup_package() - - -def pytest_sessionfinish(session): - teardown_package() - - -@pytest.hookimpl(hookwrapper=True) -def pytest_pycollect_makeitem(collector, name, obj): - global config, only_e2etest - outcome = yield - item = outcome.get_result() - if isinstance(obj, type) and issubclass(obj, TestCase): - if config is None: - item.add_marker(pytest.mark.skip("e2e tests not enabled")) - else: - item.add_marker("aioxmpp_e2etest") - elif isinstance(obj, type) and issubclass(obj, unittest.TestCase): - if only_e2etest: - item.add_marker(pytest.mark.skip("only e2e tests enabled")) - - -def pytest_runtest_setup(item): - global provisioner, e2etest_record - if item.get_closest_marker("aioxmpp_e2etest") is not None: - blocking(provisioner.setup)() - - -def pytest_runtest_call(item): - if e2etest_record: - handler = logging.FileHandler( - e2etest_record, "w", - ) - handler.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(name)s: %(levelname)s: %(message)s", - style="%" - ) - handler.setFormatter(formatter) - logger = logging.getLogger("aioxmpp.e2etest.provision") - logger.addHandler(handler) - logger.setLevel(logging.DEBUG) - - -def pytest_runtest_teardown(item): - global provisioner - if item.get_closest_marker("aioxmpp_e2etest") is not None: - blocking(provisioner.teardown)() diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__main__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__main__.py deleted file mode 100644 index 77e25fd..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__main__.py +++ /dev/null @@ -1,29 +0,0 @@ -######################################################################## -# File name: __main__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import os -import pathlib -import sys -os.chdir(str(pathlib.Path(__file__).parent.parent.parent)) -os.execv( - sys.executable, - [sys.executable, "-m", "pytest", "-p", "aioxmpp.e2etest"] + sys.argv[1:], -) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 5a36385..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/__main__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/__main__.cpython-311.pyc deleted file mode 100644 index 9fa7ef0..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/__main__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/provision.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/provision.cpython-311.pyc deleted file mode 100644 index 29cd244..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/provision.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/utils.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/utils.cpython-311.pyc deleted file mode 100644 index 68e746b..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/__pycache__/utils.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/provision.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/provision.py deleted file mode 100644 index dd42b41..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/provision.py +++ /dev/null @@ -1,810 +0,0 @@ -######################################################################## -# File name: provision.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import abc -import ast -import asyncio -import base64 -import enum -import fnmatch -import json -import logging -import random -import unittest - -import aioxmpp -import aioxmpp.disco -import aioxmpp.security_layer -import aioxmpp.connector - - -_logger = logging.getLogger(__name__) -_rng = random.SystemRandom() - - -class Quirk(enum.Enum): - """ - Enumeration of implementation quirks. - - Each enumeration member represents a quirk of an implementation. A quirk is - a behaviour of an implementation which does not directly violate standards, - but which is unfortunate in a way that it disables some features of - :mod:`aioxmpp`. - - One example of such a quirk is the rewriting of message stanza IDs which - some MUC implementations do when reflecting the messages. This breaks the - stanza tracking of :meth:`aioxmpp.muc.Room.send_tracked_message`. - - The following quirks are defined: - - .. attribute:: MUC_REWRITES_MESSAGE_ID - :annotation: https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#muc-id-rewrite - - This quirk must be configured when the environment the provisioner - provides rewrites the message IDs when they are reflected by the MUC - implementation. - - The quirk does not need to be set if the environment does not provide a - MUC implementation at all. - - .. attribute:: PUBSUB_GET_ITEMS_BY_ID_BROKEN - :annotation: https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#broken-pubsub-get-multiple-by-id - - Indicates that the "Get Items by Id" operation in the PubSub service used - is broken when more than one item is requested. - """ # NOQA: E501 - - MUC_REWRITES_MESSAGE_ID = \ - "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#muc-id-rewrite" - NO_ADHOC_PING = \ - "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#no-adhoc-ping" - MUC_NO_333 = \ - "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#muc-no-333" - BROKEN_MUC = \ - "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#broken-muc" - PUBSUB_GET_MULTIPLE_ITEMS_BY_ID_BROKEN = \ - "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#broken-pubsub-get-multiple-by-id" # NOQA: E501 - NO_PRIVATE_XML = \ - "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks#no-xep-0049" - - -def fix_quirk_str(s): - if s.startswith("#"): - return "https://zombofant.net/xmlns/aioxmpp/e2etest/quirks" + s - return s - - -def configure_tls_config(section): - """ - Generate keyword arguments for use with :meth:`.security_layer.make` from - the configuration which control the TLS behaviour of the security layer. - - :param section: Configuration section to work on. - :return: Keyword arguments for :meth:`.security_layer.make` - :rtype: :class:`dict` - - The generated keyword arguments are ``pin_type``, ``pin_store`` and - ``no_verify``. The options in the config file have the same names and the - semantics are the following: - - ``pin_store`` and ``pin_type`` can be used to configure certificate - pinning, in case the server you want to test against does not have a - certificate which passes the default OpenSSL PKIX tests. - - If set, ``pin_store`` must point to a JSON file, which consists of a single - object mapping host names to arrays of strings containing the base64 - representation of what is being pinned. This is determined by ``pin_type``, - which can be ``0`` for Public Key pinning and ``1`` for Certificate - pinning. - - There is also the ``no_verify`` option, which, if set to true, will disable - certificate verification altogether. This does not much harm if you are - testing against localhost anyways and saves the configuration nuisance for - certificate pinning. ``no_verfiy`` takes precedence over ``pin_store`` and - ``pin_type``. - """ - - no_verify = section.getboolean( - "no_verify", - fallback=False - ) - - if not no_verify and "pin_store" in section: - with open(section.get("pin_store")) as f: - pin_store = json.load(f) - pin_type = aioxmpp.security_layer.PinType( - section.getint("pin_type", fallback=0) - ) - else: - pin_store = None - pin_type = None - - return { - "pin_store": pin_store, - "pin_type": pin_type, - "no_verify": no_verify, - } - - -def configure_quirks(section): - """ - Generate a set of :class:`.Quirk` enum members from the given configuration - section. - - :param section: Configuration section to work on. - :return: Set of :class:`.Quirk` members - - This parses the configuration key ``quirks`` as a python literal (see - :func:`ast.literal_eval`). It expects a list of strings as a result. - - The strings are interpreted as :class:`.Quirk` enum values. If a string - starts with ``#``, it is prefixed with - ``https://zombofant.net/xmlns/aioxmpp/e2etest/quirks`` for easier manual - writing of the configuration. See :class:`.Quirk` for the currently defined - quirks. - """ - - quirks = ast.literal_eval(section.get("quirks", fallback="[]")) - if isinstance(quirks, (str, dict)): - raise ValueError("incorrect type for quirks setting") - return set(map(Quirk, map(fix_quirk_str, quirks))) - - -def configure_blockmap(section): - blockmap_raw = ast.literal_eval(section.get("block_features", - fallback="{}")) - return { - aioxmpp.JID.fromstr(entity): features - for entity, features in blockmap_raw.items() - } - - -def _is_feature_blocked(peer, feature, blockmap): - return any( - fnmatch.fnmatch(feature, item) - for item in blockmap.get(peer, []) - ) - - -async def discover_server_features(disco, peer, recurse_into_items=True, - blockmap={}): - """ - Use :xep:`30` service discovery to discover features supported by the - server. - - :param disco: Service discovery client which can query the `peer` server. - :type disco: :class:`aioxmpp.DiscoClient` - :param peer: The JID of the server to query - :type peer: :class:`~aioxmpp.JID` - :param recurse_into_items: If set to true, the :xep:`30` items exposed by - the server will also be queried for their - features. Only one level of recursion is - performed. - :return: A mapping which maps :xep:`30` feature vars to the JIDs at which - the service is provided. - - This uses :xep:`30` service discovery to obtain a set of features supported - at `peer`. The set of features is returned as a mapping which maps the - ``var`` values of the features to the JID at which they were discovered. - - If `recurse_into_items` is true, a :xep:`30` items query is run against - `peer`. For each JID discovered that way, :func:`discover_server_features` - is re-invoked (with `recurse_into_items` set to false). The resulting - mappings are merged with the mapping obtained from querying the features of - `peer` (existing entries are *not* overridden -- so `peer` takes - precedence). - """ - - server_info = await disco.query_info(peer) - - all_features = { - feature: [peer] - for feature in server_info.features - if not _is_feature_blocked(peer, feature, blockmap) - } - - if recurse_into_items: - server_items = await disco.query_items(peer) - features_list = await asyncio.gather( - *( - discover_server_features( - disco, - item.jid, - recurse_into_items=False, - ) - for item in server_items.items - if item.jid is not None and item.node is None - ) - ) - - for features in features_list: - for feature, providers in features.items(): - all_features.setdefault(feature, []).extend(providers) - - return all_features - - -async def discover_server_identities(disco, peer, recurse_into_items=True): - """ - Use :xep:`30` service discovery to discover identities provided by the - server. - - :param disco: Service discovery client which can query the `peer` server. - :type disco: :class:`aioxmpp.DiscoClient` - :param peer: The JID of the server to query - :type peer: :class:`~aioxmpp.JID` - :param recurse_into_items: If set to true, the :xep:`30` items exposed by - the server will also be queried for their - identities. Only one level of recursion is - performed. - :return: A mapping which maps :xep:`30` (category, type) tuples to the - JIDs at which the identity is provided. - - This uses :xep:`30` service discovery to obtain a set of identities offered - at `peer`. The set of identities is returned as a mapping which maps the - ``(category, type)`` tuples of the identities to the JID at which they were - discovered. - - If `recurse_into_items` is true, a :xep:`30` items query is run against - `peer`. For each JID discovered that way, - :func:`discover_server_identities` is re-invoked (with `recurse_into_items` - set to false). The resulting mappings are merged with the mapping obtained - from querying the identities of `peer` (existing entries are *not* - overridden -- so `peer` takes precedence). - """ - - server_info = await disco.query_info(peer) - - all_identities = { - (identity.category, identity.type_): [peer] - for identity in server_info.identities - } - - if recurse_into_items: - server_items = await disco.query_items(peer) - identities_list = await asyncio.gather( - *( - discover_server_identities( - disco, - item.jid, - recurse_into_items=False, - ) - for item in server_items.items - if item.jid is not None and item.node is None - ) - ) - - for identities in identities_list: - for identity, providers in identities.items(): - all_identities.setdefault(identity, []).extend(providers) - - return all_identities - - -class Provisioner(metaclass=abc.ABCMeta): - """ - Base class for provisioners. - - Provisioners are responsible for providing test cases with XMPP accounts - and client objects connected to these accounts, as well as information - about the environment the accounts live in. - - A provisioner must implement the following methods: - - .. automethod:: _make_client - - .. automethod:: configure - - The following methods are the API used by test cases: - - .. automethod:: get_connected_client - - .. automethod:: get_feature_provider - - .. automethod:: get_identity_provider - - .. automethod:: has_quirk - - These methods can be used by provisioners to perform plumbing tasks, such - as shutting down clients or deleting accounts: - - .. automethod:: initialise - - .. automethod:: finalise - - .. automethod:: setup - - .. automethod:: teardown - - """ - - def __init__(self, logger=_logger): - super().__init__() - self._accounts_to_dispose = [] - self._featuremap = {} - self._identitymap = {} - self._account_info = None - self._logger = logger - self.__counter = 0 - - @abc.abstractmethod - async def _make_client(self, logger): - """ - :param logger: The logger to pass to the client. - :return: Client with a fresh account. - - Construct a new :class:`aioxmpp.PresenceManagedClient` connected to a - new account. This method must be re-implemented by subclasses. - """ - - async def get_connected_client(self, presence=aioxmpp.PresenceState(True), *, - services=[], prepare=None): - """ - Return a connected client to a unique XMPP account. - - :param presence: initial presence to emit - :type presence: :class:`aioxmpp.PresenceState` - :param prepare: a coroutine run after the services - are summoned but before the client connects. - :type prepare: coroutine receiving the client - as argument - :raise OSError: if the connection failed - :raise RuntimeError: if a client could not be provisioned due to - resource constraints - :return: Connected presence managed client - :rtype: :class:`aioxmpp.PresenceManagedClient` - - Each account used by the clients returned from this method is unique; - all clients are guaranteed to have different bare JIDs. - - The clients and accounts are cleaned up after the tear down of the test - runs. Some provisioners may have a limit on the number of accounts - which can be used in the same test. - - Clients obtained from this function are cleaned up automatically on - tear down of the test. The clients are stopped and the accounts - deleted or cleared, so that each test starts with a fully fresh state. - - A coroutine may be passed as `prepare` argument. It is called - with the client as the single argument after all services in - `services` have been summoned but before the client connects, - this is for example useful to connect signals that fire early - in the connection process. - """ - id_ = self.__counter - self.__counter += 1 - self._logger.debug("obtaining client%d from %r", id_, self) - logger = self._logger.getChild("client{}".format(id_)) - client = await self._make_client(logger) - for service in services: - client.summon(service) - if prepare is not None: - await prepare(client) - cm = client.connected(presence=presence) - await cm.__aenter__() - self._accounts_to_dispose.append(cm) - return client - - def get_feature_providers(self, feature_nses): - """ - :param feature_ns: Namespace URIs to find a provider for - :type feature_ns: iterable of :class:`str` - :return: JIDs of the entities providing all features - :rtype: :class:`set` of :class:`aioxmpp.JID` - - If there is no entity supporting all requested features, the empty set - is returned. - """ - providers = set() - iterator = iter(feature_nses) - try: - first_ns = next(iterator) - except StopIteration: - return None - - providers = set(self._featuremap.get(first_ns, [])) - for feature_ns in iterator: - providers &= set(self._featuremap.get(feature_ns, [])) - return providers - - def get_feature_provider(self, feature_nses): - """ - :param feature_ns: Namespace URIs to find a provider for - :type feature_ns: iterable of :class:`str` - :return: JID of the entity providing all features - :rtype: :class:`aioxmpp.JID` - - If there is no entity supporting all requested features, :data:`None` - is returned. - """ - providers = self.get_feature_providers(feature_nses) - if not providers: - return None - return next(iter(providers)) - - def get_identity_provider(self, category, type_): - return next(iter(self._identitymap.get((category, type_), []))) - - def get_feature_subset_provider(self, feature_nses, required_subset): - required_subset = set(required_subset) - - candidates = {} - for feature_ns in feature_nses: - providers = self._featuremap.get(feature_ns, []) - for provider in providers: - candidates.setdefault(provider, set()).add(feature_ns) - - candidates = sorted( - ( - (provider, features) - for provider, features in candidates.items() - if features & required_subset == required_subset - ), - key=lambda x: (len(x[1])) - ) - - try: - return candidates.pop() - except IndexError: - return None, None - - def has_quirk(self, quirk): - """ - :param quirk: Quirk to check for - :type quirk: :class:`Quirk` - :return: true if the environment has the given quirk - """ - return quirk in self._quirks - - def has_pep(self): - """ - :return: true if the account has PEP support, false otherwise. - """ - if not self._account_info: - return False - return any(ident.category == "pubsub" and ident.type_ == "pep" - for ident in self._account_info.identities) - - @abc.abstractmethod - def configure(self, section): - """ - Read the configuration and set up the provisioner. - - :param section: mapping of config keys to values - - Subclasses will implement this to configure their account setup and - servers to use. - - .. seealso:: - :func:`configure_tls_config` - for a function which extracts TLS-related arguments for - :func:`aioxmpp.security_layer.make` - :func:`configure_quirks` - for a function which extracts a set of :class:`.Quirk` - enumeration members from the configuration - :func:`configure_blockmap` - for a function which extracts a mapping which allows to block - features from specific hosts - """ - - async def initialise(self): - """ - Called once on test framework startup. - - Subclasses may run service discovery code here to detect features of - the environment they are connected to. - - .. seealso:: - - :func:`discover_server_features` - for a function which uses :xep:`30` service discovery to find - features. - """ - - async def finalise(self): - """ - Called once on test framework shutdown (timeout of 10 seconds applies). - """ - - async def setup(self): - """ - Called before each test run. - """ - - async def teardown(self): - """ - Called after each test run. - - The default implementation cleans up the clients obtained from - :meth:`get_connected_client`. - """ - - futures = [] - for cm in self._accounts_to_dispose: - futures.append(asyncio.ensure_future( - cm.__aexit__(None, None, None) - )) - - self._accounts_to_dispose.clear() - - self._logger.debug("waiting for %d accounts to shut down", - len(futures)) - await asyncio.gather( - *futures, - return_exceptions=True - ) - - -class _AutoConfiguredProvisioner(Provisioner): - def configure(self, section): - super().configure(section) - self._blockmap = configure_blockmap(section) - - async def initialise(self): - self._logger.debug("auto-configuring provisioner %s", self) - - client = await self.get_connected_client() - disco = client.summon(aioxmpp.DiscoClient) - - self._featuremap.update(await discover_server_features( - disco, - self._domain, - blockmap=self._blockmap, - )) - - self._identitymap.update(await discover_server_identities( - disco, - self._domain, - )) - - self._logger.debug("found %d features", len(self._featuremap)) - if self._logger.isEnabledFor(logging.DEBUG): - for feature, providers in self._featuremap.items(): - self._logger.debug( - "%s provided by %s", - feature, - ", ".join(sorted(map(str, providers))) - ) - - self._account_info = await disco.query_info(None) - - # clean up state - del client - await self.teardown() - - -class AnonymousProvisioner(_AutoConfiguredProvisioner): - """ - This provisioner uses SASL ANONYMOUS to obtain accounts. - - It is dead-simple to configure: it needs a host to connect to, and - optionally some TLS and quirks configuration. The host is specified as - configuration key ``host``, TLS can be configured as documented in - :func:`configure_tls_config` and quirks are set as described in - :func:`configure_quirks`. A configuration for a locally running Prosody - instance might look like this: - - .. code-block:: ini - - [aioxmpp.e2etest.provision.AnonymousProvisioner] - host=localhost - no_verify=true - quirks=[] - - The server configured in ``host`` must support SASL ANONYMOUS and must - allow communication between the clients connected that way. It may provide - PubSub and/or MUC services, which will be auto-discovered if they are - provided in the :xep:`30` items of the server. - """ - - def configure(self, section): - super().configure(section) - self.__host = section.get("host") - self._domain = aioxmpp.JID.fromstr(section.get( - "domain", - self.__host - )) - self.__port = section.getint("port") - self.__security_layer = aioxmpp.make_security_layer( - None, - anonymous="", - **configure_tls_config( - section - ) - ) - self._quirks = configure_quirks(section) - - async def _make_client(self, logger): - override_peer = [] - if self.__port is not None: - override_peer.append( - (self.__host, self.__port, - aioxmpp.connector.STARTTLSConnector()) - ) - - return aioxmpp.PresenceManagedClient( - self._domain, - self.__security_layer, - override_peer=override_peer, - logger=logger, - ) - - -class AnyProvisioner(_AutoConfiguredProvisioner): - """ - This provisioner randomly generates usernames and uses a hardcoded password - to authenticate with the XMPP server. - - This is for use with ``mod_auth_any`` of prosody. - - It is dead-simple to configure: it needs a host to connect to, and - optionally some TLS and quirks configuration. The host is specified as - configuration key ``host``, TLS can be configured as documented in - :func:`configure_tls_config` and quirks are set as described in - :func:`configure_quirks`. A configuration for a locally running Prosody - instance might look like this: - - .. code-block:: ini - - [aioxmpp.e2etest.provision.AnyProvisioner] - host=localhost - no_verify=true - quirks=[] - - The server configured in ``host`` must allow authentication with any - username/password pair and allow communication between the clients - connected that way. It may provide PubSub and/or MUC services, which will - be auto-discovered if they are provided in the :xep:`30` items of the - server. - """ - - def configure(self, section): - super().configure(section) - self.__host = section.get("host") - self._domain = aioxmpp.JID.fromstr(section.get( - "domain", - self.__host - )) - self.__port = section.getint("port") - self.__security_layer = aioxmpp.make_security_layer( - "foobar2342", # password is irrelevant, but must be given. - **configure_tls_config( - section - ) - ) - self._quirks = configure_quirks(section) - self.__username_rng = random.Random() - self.__username_rng.seed(_rng.getrandbits(256)) - - async def _make_client(self, logger): - override_peer = [] - if self.__port is not None: - override_peer.append( - (self.__host, self.__port, - aioxmpp.connector.STARTTLSConnector()) - ) - - user = base64.b32encode( - self.__username_rng.getrandbits(128).to_bytes(128//8, 'little') - ).decode("ascii").rstrip("=") - user_jid = self._domain.replace(localpart=user) - - return aioxmpp.PresenceManagedClient( - user_jid, - self.__security_layer, - override_peer=override_peer, - logger=logger, - ) - - -class StaticPasswordProvisioner(_AutoConfiguredProvisioner): - """ - This provisioner expects a list of username/password pairs to authenticate - against the tested server. - - This is for use with servers which support neither SASL ANONYMOUS nor - a ``mod_auth_any`` equivalent. - - The configuration of this provisioner is slightly unwieldy since we do - not want to add a dependency to a more sane configuration file format. Here - is an example on how to configure a provisioner with two accounts: - - .. code-block:: ini - - [aioxmpp.e2etest.provision.StaticPasswordProvisioner] - host=localhost - accounts=[("user1", "password1"), ("user2", "password2")] - skip_on_too_few_accounts=false - - All accounts need to have exactly the same privileges on the server. The - first account will be used to auto-discover any features offered by the - test environment. - - If `skip_on_too_few_accounts` is set to true (the default is false), tests - will be skipped if the provisioner runs out of accounts instead of failing. - """ - - def _load_accounts(self, cfg): - result = [] - for username, password in ast.literal_eval(cfg): - result.append(( - aioxmpp.JID(localpart=username, domain=self._domain.domain, - resource=None), - aioxmpp.make_security_layer(password, **self.__tls_config) - )) - return result - - def configure(self, section): - super().configure(section) - self.__host = section.get("host") - self._domain = aioxmpp.JID.fromstr(section.get( - "domain", - self.__host - )) - self.__port = section.getint("port") - self.__tls_config = configure_tls_config(section) - self.__accounts = self._load_accounts(section.get("accounts")) - if len(self.__accounts) == 0: - raise RuntimeError( - "at least one account needs to be configured in the " - "StaticPasswordProvisioner section" - ) - - self.__nused_accounts = 0 - self._quirks = configure_quirks(section) - self.__username_rng = random.Random() - self.__skip_on_too_few_accounts = section.getboolean( - "skip_on_too_few_accounts", - fallback=False, - ) - - async def _make_client(self, logger): - override_peer = [] - if self.__port is not None: - override_peer.append( - (self.__host, self.__port, - aioxmpp.connector.STARTTLSConnector()) - ) - - next_account = self.__nused_accounts - try: - address, security_layer = self.__accounts[next_account] - except IndexError: - err = ( - "not enough accounts; needed at least one more account " - "after already using {} accounts".format(next_account) - ) - if self.__skip_on_too_few_accounts: - raise unittest.SkipTest(err) - raise RuntimeError(err) - - self.__nused_accounts += 1 - - return aioxmpp.PresenceManagedClient( - address, - security_layer, - override_peer=override_peer, - logger=logger, - ) - - async def teardown(self): - await super().teardown() - self.__nused_accounts = 0 diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/utils.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/utils.py deleted file mode 100644 index 5400198..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/e2etest/utils.py +++ /dev/null @@ -1,42 +0,0 @@ -######################################################################## -# File name: utils.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -import functools - - -def blocking(f): - """ - The decorated coroutine function is run using the - :meth:`~asyncio.AbstractEventLoop.run_until_complete` method of the current - (at the time of call) event loop. - - The decorated function behaves like a normal function and is not a - coroutine function. - - This decorator must be applied to a coroutine function (or method). - """ - - @functools.wraps(f) - def wrapped(*args, **kwargs): - loop = asyncio.get_event_loop() - return loop.run_until_complete(f(*args, **kwargs)) - return wrapped diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__init__.py deleted file mode 100644 index 4b28973..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__init__.py +++ /dev/null @@ -1,63 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.entitycaps` --- Entity Capabilities support (:xep:`390`, :xep:`0115`) -#################################################################################### - -This module provides support for :xep:`XEP-0115 (Entity Capabilities) <0115>` -and :xep:`XEP-0390 (Entity Capabilities 2.0) <0390>`. To use it, -:meth:`.Client.summon` the :class:`aioxmpp.EntityCapsService` on a -:class:`~.Client`. See the service documentation for more information. - -.. versionadded:: 0.5 - -.. versionchanged:: 0.9 - - Support for :xep:`390` was added. - -Service -======= - -.. currentmodule:: aioxmpp - -.. autoclass:: EntityCapsService - -.. currentmodule:: aioxmpp.entitycaps - -.. class:: Service - - Alias of :class:`.EntityCapsService`. - - .. deprecated:: 0.8 - - The alias will be removed in 1.0. - -.. autoclass:: Cache - -.. currentmodule:: aioxmpp.entitycaps.xso - - -""" # NOQA: E501 - -from .service import EntityCapsService, Cache # NOQA: F401 -from . import xso # NOQA: F401 -Service = EntityCapsService diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 2cb0bca..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/caps115.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/caps115.cpython-311.pyc deleted file mode 100644 index 7a6a128..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/caps115.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/caps390.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/caps390.cpython-311.pyc deleted file mode 100644 index d839326..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/caps390.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/common.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/common.cpython-311.pyc deleted file mode 100644 index d8684e8..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/common.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/service.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/service.cpython-311.pyc deleted file mode 100644 index e507f6a..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/service.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index 0b6b5b0..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/caps115.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/caps115.py deleted file mode 100644 index 9c7fac6..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/caps115.py +++ /dev/null @@ -1,175 +0,0 @@ -######################################################################## -# File name: caps115.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import base64 -import collections -import hashlib -import pathlib -import urllib.parse - -from xml.sax.saxutils import escape - -from .common import AbstractKey, AbstractImplementation -from . import xso as caps_xso - - -def build_identities_string(identities): - identities = [ - b"/".join([ - escape(identity.category).encode("utf-8"), - escape(identity.type_).encode("utf-8"), - escape(str(identity.lang or "")).encode("utf-8"), - escape(identity.name or "").encode("utf-8"), - ]) - for identity in identities - ] - - if len(set(identities)) != len(identities): - raise ValueError("duplicate identity") - - identities.sort() - identities.append(b"") - return b"<".join(identities) - - -def build_features_string(features): - features = list(escape(feature).encode("utf-8") for feature in features) - - if len(set(features)) != len(features): - raise ValueError("duplicate feature") - - features.sort() - features.append(b"") - return b"<".join(features) - - -def build_forms_string(forms): - types = set() - forms_list = [] - for form in forms: - try: - form_types = set( - value - for field in form.fields.filter(attrs={"var": "FORM_TYPE"}) - for value in field.values - ) - except KeyError: - continue - - if len(form_types) > 1: - raise ValueError("form with multiple types") - elif not form_types: - continue - - type_ = escape(next(iter(form_types))).encode("utf-8") - if type_ in types: - raise ValueError("multiple forms of type {!r}".format(type_)) - types.add(type_) - forms_list.append((type_, form)) - forms_list.sort() - - parts = [] - - for type_, form in forms_list: - parts.append(type_) - - field_list = sorted( - ( - (escape(field.var).encode("utf-8"), field.values) - for field in form.fields - if field.var != "FORM_TYPE" - ), - key=lambda x: x[0] - ) - - for var, values in field_list: - parts.append(var) - parts.extend(sorted( - escape(value).encode("utf-8") for value in values - )) - - parts.append(b"") - return b"<".join(parts) - - -def hash_query(query, algo): - hashimpl = hashlib.new(algo) - hashimpl.update( - build_identities_string(query.identities) - ) - hashimpl.update( - build_features_string(query.features) - ) - hashimpl.update( - build_forms_string(query.exts) - ) - - return base64.b64encode(hashimpl.digest()).decode("ascii") - - -Key = collections.namedtuple("Key", ["algo", "node"]) - - -class Key(Key, AbstractKey): - @property - def path(self): - quoted = urllib.parse.quote(self.node, safe="") - return (pathlib.Path("hashes") / - "{}_{}.xml".format(self.algo, quoted)) - - @property - def ver(self): - return self.node.rsplit("#", 1)[1] - - def verify(self, query_response): - digest_b64 = hash_query(query_response, self.algo.replace("-", "")) - return self.ver == digest_b64 - - -class Implementation(AbstractImplementation): - def __init__(self, node, **kwargs): - super().__init__(**kwargs) - self.__node = node - - def extract_keys(self, obj): - caps = obj.xep0115_caps - if caps is None or caps.hash_ is None: - return - - yield Key(caps.hash_, "{}#{}".format(caps.node, caps.ver)) - - def put_keys(self, keys, presence): - key, = keys - - presence.xep0115_caps = caps_xso.Caps115( - self.__node, - key.ver, - key.algo, - ) - - def calculate_keys(self, query_response): - yield Key( - "sha-1", - "{}#{}".format( - self.__node, - hash_query(query_response, "sha1"), - ) - ) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/caps390.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/caps390.py deleted file mode 100644 index 68f490e..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/caps390.py +++ /dev/null @@ -1,192 +0,0 @@ -######################################################################## -# File name: caps390.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import base64 -import pathlib -import collections -import urllib.parse - -import aioxmpp.hashes - -from .common import AbstractKey -from . import xso as caps_xso - - -def _process_features(features): - """ - Generate the `Features String` from an iterable of features. - - :param features: The features to generate the features string from. - :type features: :class:`~collections.abc.Iterable` of :class:`str` - :return: The `Features String` - :rtype: :class:`bytes` - - Generate the `Features String` from the given `features` as specified in - :xep:`390`. - """ - parts = [ - feature.encode("utf-8")+b"\x1f" - for feature in features - ] - parts.sort() - return b"".join(parts)+b"\x1c" - - -def _process_identity(identity): - category = (identity.category or "").encode("utf-8")+b"\x1f" - type_ = (identity.type_ or "").encode("utf-8")+b"\x1f" - lang = str(identity.lang or "").encode("utf-8")+b"\x1f" - name = (identity.name or "").encode("utf-8")+b"\x1f" - - return b"".join([category, type_, lang, name]) + b"\x1e" - - -def _process_identities(identities): - """ - Generate the `Identities String` from an iterable of identities. - - :param identities: The identities to generate the features string from. - :type identities: :class:`~collections.abc.Iterable` of - :class:`~.disco.xso.Identity` - :return: The `Identities String` - :rtype: :class:`bytes` - - Generate the `Identities String` from the given `identities` as specified - in :xep:`390`. - """ - parts = [ - _process_identity(identity) - for identity in identities - ] - parts.sort() - return b"".join(parts)+b"\x1c" - - -def _process_field(field): - parts = [ - (value or "").encode("utf-8") + b"\x1f" - for value in field.values - ] - - parts.insert(0, field.var.encode("utf-8")+b"\x1f") - return b"".join(parts)+b"\x1e" - - -def _process_form(form): - parts = [ - _process_field(form) - for form in form.fields - ] - - parts.sort() - return b"".join(parts)+b"\x1d" - - -def _process_extensions(exts): - """ - Generate the `Extensions String` from an iterable of data forms. - - :param exts: The data forms to generate the extensions string from. - :type exts: :class:`~collections.abc.Iterable` of - :class:`~.forms.xso.Data` - :return: The `Extensions String` - :rtype: :class:`bytes` - - Generate the `Extensions String` from the given `exts` as specified - in :xep:`390`. - """ - parts = [ - _process_form(form) - for form in exts - ] - parts.sort() - return b"".join(parts)+b"\x1c" - - -def _get_hash_input(info): - return b"".join([ - _process_features(info.features), - _process_identities(info.identities), - _process_extensions(info.exts) - ]) - - -def _calculate_hash(algo, hash_input): - impl = aioxmpp.hashes.hash_from_algo(algo) - impl.update(hash_input) - return impl.digest() - - -Key = collections.namedtuple("Key", ["algo", "digest"]) - - -class Key(Key, AbstractKey): - @property - def node(self): - return "urn:xmpp:caps#{}.{}".format( - self.algo, - base64.b64encode(self.digest).decode("ascii") - ) - - @property - def path(self): - encoded = base64.b32encode( - self.digest - ).decode("ascii").rstrip("=").lower() - return (pathlib.Path("caps2") / - urllib.parse.quote(self.algo, safe="") / - encoded[:2] / - encoded[2:4] / - "{}.xml".format(encoded[4:])) - - def verify(self, info): - if not isinstance(info, bytes): - info = _get_hash_input(info) - digest = _calculate_hash(self.algo, info) - return digest == self.digest - - -class Implementation: - def __init__(self, algorithms, **kwargs): - super().__init__(**kwargs) - self.__algorithms = algorithms - - def extract_keys(self, presence): - if presence.xep0390_caps is None: - return () - - return ( - Key(algo, digest) - for algo, digest in presence.xep0390_caps.digests.items() - if aioxmpp.hashes.is_algo_supported(algo) - ) - - def put_keys(self, keys, presence): - presence.xep0390_caps = caps_xso.Caps390() - presence.xep0390_caps.digests.update({ - key.algo: key.digest - for key in keys - }) - - def calculate_keys(self, query_response): - input = _get_hash_input(query_response) - for algo in self.__algorithms: - yield Key(algo, _calculate_hash(algo, input)) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/common.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/common.py deleted file mode 100644 index 1f71075..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/common.py +++ /dev/null @@ -1,105 +0,0 @@ -######################################################################## -# File name: common.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import abc - - -class AbstractKey(metaclass=abc.ABCMeta): - @abc.abstractproperty - def path(self): - """ - Return the file system path relative to the root of a file-system based - caps database for this key. - - The path includes all information of the key. Components of the path do - not exceed 255 codepoints and use only ASCII codepoints. - - If it is not possible to create such a path, :class:`ValueError` is - raised. - """ - - @abc.abstractmethod - def verify(self, query_response): - """ - Verify whether the cache key matches a piece of service discovery - information. - - :param query_response: The full :xep:`30` disco#info query response. - :type query_response: :class:`~.disco.xso.InfoQuery` - :rtype: :class:`bool` - :return: true if the key matches and false otherwise. - """ - - -class AbstractImplementation(metaclass=abc.ABCMeta): - @abc.abstractmethod - def extract_keys(self, presence): - """ - Extract cache keys from a presence stanza. - - :param presence: Presence stanza to extract cache keys from. - :type presence: :class:`aioxmpp.Presence` - :rtype: :class:`~collections.abc.Iterable` of :class:`AbstractKey` - :return: The cache keys from the presence stanza. - - The resulting iterable may be empty if the presence stanza does not - carry any capabilities information with it. - - The resulting iterable cannot be iterated over multiple times. - """ - - @abc.abstractmethod - def put_keys(self, keys, presence): - """ - Insert cache keys into a presence stanza. - - :param keys: An iterable of cache keys to insert. - :type keys: :class:`~collections.abc.Iterable` of :class:`AbstractKey` - objects - :param presence: The presence stanza into which the cache keys shall be - injected. - :type presence: :class:`aioxmpp.Presence` - - The presence stanza is modified in-place. - """ - - @abc.abstractmethod - def calculate_keys(self, query_response): - """ - Calculate the cache keys for a disco#info response. - - :param query_response: The full :xep:`30` disco#info query response. - :type query_response: :class:`~.disco.xso.InfoQuery` - :rtype: :class:`~collections.abc.Iterable` of :class:`AbstractKey` - :return: An iterable of the cache keys for the disco#info response. - - .. - - :param identities: The identities of the disco#info response. - :type identities: :class:`~collections.abc.Iterable` of - :class:`~.disco.xso.Identity` - :param features: The features of the disco#info response. - :type features: :class:`~collections.abc.Iterable` of - :class:`str` - :param features: The extensions of the disco#info response. - :type features: :class:`~collections.abc.Iterable` of - :class:`~.forms.xso.Data` - """ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/service.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/service.py deleted file mode 100644 index 4c2d60b..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/service.py +++ /dev/null @@ -1,513 +0,0 @@ -######################################################################## -# File name: service.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import asyncio -import collections -import copy -import functools -import logging -import os -import tempfile - -import aioxmpp.callbacks -import aioxmpp.disco as disco -import aioxmpp.service -import aioxmpp.utils -import aioxmpp.xml -import aioxmpp.xso - -from aioxmpp.utils import namespaces - -from . import caps115, caps390 - - -logger = logging.getLogger("aioxmpp.entitycaps") - - -class Cache: - """ - This provides a two-level cache for entity capabilities information. The - idea is to have a trusted database, e.g. installed system-wide or shipped - with :mod:`aioxmpp` and in addition a user-level database which is - automatically filled with hashes which have been found by the - :class:`Service`. - - The trusted database is taken as read-only and overrides the user-collected - database. When a hash is in both databases, it is removed from the - user-collected database (to save space). - - In addition to serving the databases, it provides deduplication for queries - by holding a cache of futures looking up the same hash. - - Database management (user API): - - .. automethod:: set_system_db_path - - .. automethod:: set_user_db_path - - Queries (API intended for :class:`Service`): - - .. automethod:: create_query_future - - .. automethod:: lookup_in_database - - .. automethod:: lookup - """ - - def __init__(self): - self._lookup_cache = {} - self._memory_overlay = {} - self._system_db_path = None - self._user_db_path = None - - def _erase_future(self, key, fut): - try: - existing = self._lookup_cache[key] - except KeyError: - pass - else: - if existing is fut: - del self._lookup_cache[key] - - def set_system_db_path(self, path): - self._system_db_path = path - - def set_user_db_path(self, path): - self._user_db_path = path - - def lookup_in_database(self, key): - try: - result = self._memory_overlay[key] - except KeyError: - pass - else: - logger.debug("memory cache hit: %s", key) - return result - - key_path = key.path - - if self._system_db_path is not None: - try: - f = ( - self._system_db_path / key_path - ).open("rb") - except OSError: - pass - else: - logger.debug("system db hit: %s", key) - with f: - return aioxmpp.xml.read_single_xso(f, disco.xso.InfoQuery) - - if self._user_db_path is not None: - try: - f = ( - self._user_db_path / key_path - ).open("rb") - except OSError: - pass - else: - logger.debug("user db hit: %s", key) - with f: - return aioxmpp.xml.read_single_xso(f, disco.xso.InfoQuery) - - raise KeyError(key) - - async def lookup(self, key): - """ - Look up the given `node` URL using the given `hash_` first in the - database and then by waiting on the futures created with - :meth:`create_query_future` for that node URL and hash. - - If the hash is not in the database, :meth:`lookup` iterates as long as - there are pending futures for the given `hash_` and `node`. If there - are no pending futures, :class:`KeyError` is raised. If a future raises - a :class:`ValueError`, it is ignored. If the future returns a value, it - is used as the result. - """ - try: - result = self.lookup_in_database(key) - except KeyError: - pass - else: - return result - - while True: - fut = self._lookup_cache[key] - try: - result = await fut - except ValueError: - continue - else: - return result - - def create_query_future(self, key): - """ - Create and return a :class:`asyncio.Future` for the given `hash_` - function and `node` URL. The future is referenced internally and used - by any calls to :meth:`lookup` which are made while the future is - pending. The future is removed from the internal storage automatically - when a result or exception is set for it. - - This allows for deduplication of queries for the same hash. - """ - fut = asyncio.Future() - fut.add_done_callback( - functools.partial(self._erase_future, key) - ) - self._lookup_cache[key] = fut - return fut - - def add_cache_entry(self, key, entry): - """ - Add the given `entry` (which must be a :class:`~.disco.xso.InfoQuery` - instance) to the user-level database keyed with the hash function type - `hash_` and the `node` URL. The `entry` is **not** validated to - actually map to `node` with the given `hash_` function, it is expected - that the caller performs the validation. - """ - copied_entry = copy.copy(entry) - self._memory_overlay[key] = copied_entry - if self._user_db_path is not None: - asyncio.ensure_future(asyncio.get_event_loop().run_in_executor( - None, - writeback, - self._user_db_path / key.path, - entry.captured_events)) - - -class EntityCapsService(aioxmpp.service.Service): - """ - Make use and provide service discovery information in presence broadcasts. - - This service implements :xep:`0115` and :xep:`0390`, transparently. - Besides loading the service, no interaction is required to get some of - the benefits of :xep:`0115` and :xep:`0390`. - - Two additional things need to be done by users to get full support and - performance: - - 1. To make sure that peers are always up-to-date with the current - capabilities, it is required that users listen on the - :meth:`on_ver_changed` signal and re-emit their current presence when it - fires. - - .. note:: - - Keeping peers up-to-date is a MUST in :xep:`390`. - - The service takes care of attaching capabilities information on the - outgoing stanza, using a stanza filter. - - .. warning:: - - :meth:`on_ver_changed` may be emitted at a considerable rate when - services are loaded or certain features (such as PEP-based services) - are configured. It is up to the application to limit the rate at - which presences are sent for the sole purpose of updating peers with - new capability information. - - 2. Users should use a process-wide :class:`Cache` instance and assign it to - the :attr:`cache` of each :class:`.entitycaps.Service` they use. This - improves performance by sharing (verified) hashes among :class:`Service` - instances. - - In addition, the hashes should be saved and restored on shutdown/start - of the process. See the :class:`Cache` for details. - - .. signal:: on_ver_changed - - The signal emits whenever the Capability Hashset of the local client - changes. This happens when the set of features or identities announced - in the :class:`.DiscoServer` changes. - - .. autoattribute:: cache - - .. autoattribute:: xep115_support - - .. autoattribute:: xep390_support - - .. versionchanged:: 0.8 - - This class was formerly known as :class:`aioxmpp.entitycaps.Service`. It - is still available under that name, but the alias will be removed in - 1.0. - - .. versionchanged:: 0.9 - - Support for :xep:`390` was added. - - """ - - ORDER_AFTER = { - disco.DiscoClient, - disco.DiscoServer, - } - - NODE = "http://aioxmpp.zombofant.net/" - - on_ver_changed = aioxmpp.callbacks.Signal() - - def __init__(self, node, **kwargs): - super().__init__(node, **kwargs) - - self.__current_keys = {} - self._cache = Cache() - - self.disco_server = self.dependencies[disco.DiscoServer] - self.disco_client = self.dependencies[disco.DiscoClient] - - self.__115 = caps115.Implementation(self.NODE) - self.__390 = caps390.Implementation( - aioxmpp.hashes.default_hash_algorithms - ) - - self.__active_hashsets = [] - self.__key_users = collections.Counter() - - @property - def xep115_support(self): - """ - Boolean to control whether :xep:`115` support is enabled or not. - - Defaults to :data:`True`. - - If set to false, inbound :xep:`115` capabilities will not be processed - and no :xep:`115` capabilities will be emitted. - - .. note:: - - At some point, this will default to :data:`False` to save - bandwidth. The exact release depends on the adoption of :xep:`390` - and will be announced in time. If you depend on :xep:`115` support, - set this boolean to :data:`True`. - - The attribute itself will not be removed until :xep:`115` support - is removed from :mod:`aioxmpp` entirely, which is unlikely to - happen any time soon. - - .. versionadded:: 0.9 - """ - - return self._xep115_feature.enabled - - @xep115_support.setter - def xep115_support(self, value): - self._xep115_feature.enabled = value - - @property - def xep390_support(self): - """ - Boolean to control whether :xep:`390` support is enabled or not. - - Defaults to :data:`True`. - - If set to false, inbound :xep:`390` Capability Hash Sets will not be - processed and no Capability Hash Sets or Capability Nodes will be - generated. - - The hash algorithms used for generating Capability Hash Sets are those - from :data:`aioxmpp.hashes.default_hash_algorithms`. - """ - return self._xep390_feature.enabled - - @xep390_support.setter - def xep390_support(self, value): - self._xep390_feature.enabled = value - - @property - def cache(self): - """ - The :class:`Cache` instance used for this :class:`Service`. Deleting - this attribute will automatically create a new :class:`Cache` instance. - - The attribute can be used to share a single :class:`Cache` among - multiple :class:`Service` instances. - """ - return self._cache - - @cache.setter - def cache(self, v): - self._cache = v - - @cache.deleter - def cache(self): - self._cache = Cache() - - @aioxmpp.service.depsignal( - disco.DiscoServer, - "on_info_changed") - def _info_changed(self): - self.logger.debug("info changed, scheduling re-calculation of version") - asyncio.get_event_loop().call_soon( - self.update_hash - ) - - async def _shutdown(self): - for group in self.__current_keys.values(): - for key in group: - self.disco_server.unmount_node(key.node) - - async def query_and_cache(self, jid, key, fut): - data = await self.disco_client.query_info( - jid, - node=key.node, - require_fresh=True, - no_cache=True, # the caps node is never queried by apps - ) - - try: - if key.verify(data): - self.cache.add_cache_entry(key, data) - fut.set_result(data) - else: - raise ValueError("hash mismatch") - except ValueError as exc: - fut.set_exception(exc) - - return data - - async def lookup_info(self, jid, keys): - for key in keys: - try: - info = await self.cache.lookup(key) - except KeyError: - continue - - self.logger.debug("found %s in cache", key) - return info - - first_key = keys[0] - self.logger.debug("using key %s to query peer", first_key) - fut = self.cache.create_query_future(first_key) - info = await self.query_and_cache( - jid, first_key, fut - ) - self.logger.debug("%s maps to %r", key, info) - - return info - - @aioxmpp.service.outbound_presence_filter - def handle_outbound_presence(self, presence): - if (presence.type_ == aioxmpp.structs.PresenceType.AVAILABLE - and self.__active_hashsets): - current_hashset = self.__active_hashsets[-1] - - try: - keys = current_hashset[self.__115] - except KeyError: - pass - else: - self.__115.put_keys(keys, presence) - - try: - keys = current_hashset[self.__390] - except KeyError: - pass - else: - self.__390.put_keys(keys, presence) - - return presence - - @aioxmpp.service.inbound_presence_filter - def handle_inbound_presence(self, presence): - keys = [] - - if self.xep390_support: - keys.extend(self.__390.extract_keys(presence)) - - if self.xep115_support: - keys.extend(self.__115.extract_keys(presence)) - - if keys: - lookup_task = aioxmpp.utils.LazyTask( - self.lookup_info, - presence.from_, - keys, - ) - self.disco_client.set_info_future( - presence.from_, - None, - lookup_task - ) - - return presence - - def _push_hashset(self, node, hashset): - if self.__active_hashsets and hashset == self.__active_hashsets[-1]: - return False - - for group in hashset.values(): - for key in group: - if not self.__key_users[key.node]: - self.disco_server.mount_node(key.node, node) - self.__key_users[key.node] += 1 - self.__active_hashsets.append(hashset) - - for expired in self.__active_hashsets[:-3]: - for group in expired.values(): - for key in group: - self.__key_users[key.node] -= 1 - if not self.__key_users[key.node]: - self.disco_server.unmount_node(key.node) - del self.__key_users[key.node] - - del self.__active_hashsets[:-3] - - return True - - def update_hash(self): - node = disco.StaticNode.clone(self.disco_server) - info = node.as_info_xso() - - new_hashset = {} - - if self.xep115_support: - new_hashset[self.__115] = set(self.__115.calculate_keys(info)) - - if self.xep390_support: - new_hashset[self.__390] = set(self.__390.calculate_keys(info)) - - self.logger.debug("new hashset=%r", new_hashset) - - if self._push_hashset(node, new_hashset): - self.on_ver_changed() - - # declare those at the bottom so that on_ver_changed gets emitted when the - # service is instantiated - _xep115_feature = disco.register_feature(namespaces.xep0115_caps) - _xep390_feature = disco.register_feature(namespaces.xep0390_caps) - - -def writeback(path, captured_events): - aioxmpp.utils.mkdir_exist_ok(path.parent) - with tempfile.NamedTemporaryFile(dir=str(path.parent), - delete=False) as tmpf: - try: - generator = aioxmpp.xml.XMPPXMLGenerator( - tmpf, - short_empty_elements=True) - generator.startDocument() - aioxmpp.xso.events_to_sax(captured_events, generator) - generator.endDocument() - except: # NOQA - os.unlink(tmpf.name) - raise - os.replace(tmpf.name, str(path)) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/xso.py deleted file mode 100644 index cbc1684..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/entitycaps/xso.py +++ /dev/null @@ -1,82 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import aioxmpp.hashes -import aioxmpp.stanza as stanza -import aioxmpp.xso as xso - -from aioxmpp.utils import namespaces - - -namespaces.xep0115_caps = "http://jabber.org/protocol/caps" -namespaces.xep0390_caps = "urn:xmpp:caps" - - -class Caps115(xso.XSO): - """ - An entity capabilities extension for :class:`~.Presence`. - - .. attribute:: node - - The indicated node, for use with the corresponding info query. - - .. attribute:: hash_ - - The hash algorithm used. This is :data:`None` if the legacy format is - used. - - .. attribute:: ver - - The version (in the legacy format) or the calculated hash. - - .. attribute:: ext - - Only there for backwards compatibility. Not used anymore. - - """ - TAG = (namespaces.xep0115_caps, "c") - - node = xso.Attr("node") - - hash_ = xso.Attr( - "hash", - validator=xso.Nmtoken(), - validate=xso.ValidateMode.FROM_CODE, - default=None # to check for legacy - ) - - ver = xso.Attr("ver") - - ext = xso.Attr("ext", default=None) - - def __init__(self, node, ver, hash_): - super().__init__() - self.node = node - self.ver = ver - self.hash_ = hash_ - - -class Caps390(aioxmpp.hashes.HashesParent, xso.XSO): - TAG = namespaces.xep0390_caps, "c" - - -stanza.Presence.xep0115_caps = xso.Child([Caps115]) -stanza.Presence.xep0390_caps = xso.Child([Caps390]) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/errors.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/errors.py deleted file mode 100644 index 3929076..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/errors.py +++ /dev/null @@ -1,644 +0,0 @@ -######################################################################## -# File name: errors.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.errors` --- Exception classes -############################################ - -Exception classes mapping to XMPP stream errors -=============================================== - -.. autoclass:: StreamError - -.. autoclass:: StreamErrorCondition - -Exception classes mapping to XMPP stanza errors -=============================================== - -.. autoclass:: StanzaError - -.. autoclass:: XMPPError - -.. currentmodule:: aioxmpp - -.. autoclass:: ErrorCondition - -.. autoclass:: XMPPAuthError - -.. autoclass:: XMPPModifyError - -.. autoclass:: XMPPCancelError - -.. autoclass:: XMPPWaitError - -.. autoclass:: XMPPContinueError - -.. currentmodule:: aioxmpp.errors - -.. autoclass:: ErroneousStanza - -Stream negotiation exceptions -============================= - -.. autoclass:: StreamNegotiationFailure - -.. autoclass:: SecurityNegotiationFailure - -.. autoclass:: SASLUnavailable - -.. autoclass:: TLSFailure - -.. autoclass:: TLSUnavailable - -I18N exceptions -=============== - -.. autoclass:: UserError - -.. autoclass:: UserValueError - -Other exceptions -================ - -.. autoclass:: MultiOSError - -.. autoclass:: GatherError - -""" -import enum -import gettext -import warnings - -from . import xso, i18n, structs - -from .utils import namespaces - - -def format_error_text( - condition, - text=None, - application_defined_condition=None): - error_tag = xso.tag_to_str(condition.value) - if application_defined_condition is not None: - error_tag += "/{}".format( - xso.tag_to_str(application_defined_condition.TAG) - ) - if text: - error_tag += " ({!r})".format(text) - return error_tag - - -class ErrorCondition(structs.CompatibilityMixin, xso.XSOEnumMixin, enum.Enum): - """ - Enumeration to represent a :rfc:`6120` stanza error condition. Please - see :rfc:`6120`, section 8.3.3, for the semantics of the individual - conditions. - - .. versionadded:: 0.10 - - .. attribute:: BAD_REQUEST - :annotation: = namespaces.stanzas, "bad-request" - - .. attribute:: CONFLICT - :annotation: = namespaces.stanzas, "conflict" - - .. attribute:: FEATURE_NOT_IMPLEMENTED - :annotation: = namespaces.stanzas, "feature-not-implemented" - - .. attribute:: FORBIDDEN - :annotation: = namespaces.stanzas, "forbidden" - - .. attribute:: GONE - :annotation: = namespaces.stanzas, "gone" - - .. attribute:: xso_class - - .. attribute:: new_address - - The text content of the ```` element represtenting the - URI at which the entity can now be found. - - May be :data:`None` if there is no such URI. - - .. attribute:: INTERNAL_SERVER_ERROR - :annotation: = namespaces.stanzas, "internal-server-error" - - .. attribute:: ITEM_NOT_FOUND - :annotation: = namespaces.stanzas, "item-not-found" - - .. attribute:: JID_MALFORMED - :annotation: = namespaces.stanzas, "jid-malformed" - - .. attribute:: NOT_ACCEPTABLE - :annotation: = namespaces.stanzas, "not-acceptable" - - .. attribute:: NOT_ALLOWED - :annotation: = namespaces.stanzas, "not-allowed" - - .. attribute:: NOT_AUTHORIZED - :annotation: = namespaces.stanzas, "not-authorized" - - .. attribute:: POLICY_VIOLATION - :annotation: = namespaces.stanzas, "policy-violation" - - .. attribute:: RECIPIENT_UNAVAILABLE - :annotation: = namespaces.stanzas, "recipient-unavailable" - - .. attribute:: REDIRECT - :annotation: = namespaces.stanzas, "redirect" - - .. attribute:: xso_class - - .. attribute:: new_address - - The text content of the ```` element represtenting - the URI at which the entity can currently be found. - - May be :data:`None` if there is no such URI. - - .. attribute:: REGISTRATION_REQUIRED - :annotation: = namespaces.stanzas, "registration-required" - - .. attribute:: REMOTE_SERVER_NOT_FOUND - :annotation: = namespaces.stanzas, "remote-server-not-found" - - .. attribute:: REMOTE_SERVER_TIMEOUT - :annotation: = namespaces.stanzas, "remote-server-timeout" - - .. attribute:: RESOURCE_CONSTRAINT - :annotation: = namespaces.stanzas, "resource-constraint" - - .. attribute:: SERVICE_UNAVAILABLE - :annotation: = namespaces.stanzas, "service-unavailable" - - .. attribute:: SUBSCRIPTION_REQUIRED - :annotation: = namespaces.stanzas, "subscription-required" - - .. attribute:: UNDEFINED_CONDITION - :annotation: = namespaces.stanzas, "undefined-condition" - - .. attribute:: UNEXPECTED_REQUEST - :annotation: = namespaces.stanzas, "unexpected-request" - - """ - - BAD_REQUEST = (namespaces.stanzas, "bad-request") - CONFLICT = (namespaces.stanzas, "conflict") - FEATURE_NOT_IMPLEMENTED = (namespaces.stanzas, "feature-not-implemented") - FORBIDDEN = (namespaces.stanzas, "forbidden") - GONE = (namespaces.stanzas, "gone") - INTERNAL_SERVER_ERROR = (namespaces.stanzas, "internal-server-error") - ITEM_NOT_FOUND = (namespaces.stanzas, "item-not-found") - JID_MALFORMED = (namespaces.stanzas, "jid-malformed") - NOT_ACCEPTABLE = (namespaces.stanzas, "not-acceptable") - NOT_ALLOWED = (namespaces.stanzas, "not-allowed") - NOT_AUTHORIZED = (namespaces.stanzas, "not-authorized") - POLICY_VIOLATION = (namespaces.stanzas, "policy-violation") - RECIPIENT_UNAVAILABLE = (namespaces.stanzas, "recipient-unavailable") - REDIRECT = (namespaces.stanzas, "redirect") - REGISTRATION_REQUIRED = (namespaces.stanzas, "registration-required") - REMOTE_SERVER_NOT_FOUND = (namespaces.stanzas, "remote-server-not-found") - REMOTE_SERVER_TIMEOUT = (namespaces.stanzas, "remote-server-timeout") - RESOURCE_CONSTRAINT = (namespaces.stanzas, "resource-constraint") - SERVICE_UNAVAILABLE = (namespaces.stanzas, "service-unavailable") - SUBSCRIPTION_REQUIRED = (namespaces.stanzas, "subscription-required") - UNDEFINED_CONDITION = (namespaces.stanzas, "undefined-condition") - UNEXPECTED_REQUEST = (namespaces.stanzas, "unexpected-request") - - -ErrorCondition.GONE.xso_class.new_address = xso.Text() -ErrorCondition.REDIRECT.xso_class.new_address = xso.Text() - - -class StreamErrorCondition(structs.CompatibilityMixin, - xso.XSOEnumMixin, - enum.Enum): - """ - Enumeration to represent a :rfc:`6120` stream error condition. Please - see :rfc:`6120`, section 4.9.3, for the semantics of the individual - conditions. - - .. versionadded:: 0.10 - - .. attribute:: BAD_FORMAT - :annotation: = (namespaces.streams, "bad-format") - - .. attribute:: BAD_NAMESPACE_PREFIX - :annotation: = (namespaces.streams, "bad-namespace-prefix") - - .. attribute:: CONFLICT - :annotation: = (namespaces.streams, "conflict") - - .. attribute:: CONNECTION_TIMEOUT - :annotation: = (namespaces.streams, "connection-timeout") - - .. attribute:: HOST_GONE - :annotation: = (namespaces.streams, "host-gone") - - .. attribute:: HOST_UNKNOWN - :annotation: = (namespaces.streams, "host-unknown") - - .. attribute:: IMPROPER_ADDRESSING - :annotation: = (namespaces.streams, "improper-addressing") - - .. attribute:: INTERNAL_SERVER_ERROR - :annotation: = (namespaces.streams, "internal-server-error") - - .. attribute:: INVALID_FROM - :annotation: = (namespaces.streams, "invalid-from") - - .. attribute:: INVALID_NAMESPACE - :annotation: = (namespaces.streams, "invalid-namespace") - - .. attribute:: INVALID_XML - :annotation: = (namespaces.streams, "invalid-xml") - - .. attribute:: NOT_AUTHORIZED - :annotation: = (namespaces.streams, "not-authorized") - - .. attribute:: NOT_WELL_FORMED - :annotation: = (namespaces.streams, "not-well-formed") - - .. attribute:: POLICY_VIOLATION - :annotation: = (namespaces.streams, "policy-violation") - - .. attribute:: REMOTE_CONNECTION_FAILED - :annotation: = (namespaces.streams, "remote-connection-failed") - - .. attribute:: RESET - :annotation: = (namespaces.streams, "reset") - - .. attribute:: RESOURCE_CONSTRAINT - :annotation: = (namespaces.streams, "resource-constraint") - - .. attribute:: RESTRICTED_XML - :annotation: = (namespaces.streams, "restricted-xml") - - .. attribute:: SEE_OTHER_HOST - :annotation: = (namespaces.streams, "see-other-host") - - .. attribute:: SYSTEM_SHUTDOWN - :annotation: = (namespaces.streams, "system-shutdown") - - .. attribute:: UNDEFINED_CONDITION - :annotation: = (namespaces.streams, "undefined-condition") - - .. attribute:: UNSUPPORTED_ENCODING - :annotation: = (namespaces.streams, "unsupported-encoding") - - .. attribute:: UNSUPPORTED_FEATURE - :annotation: = (namespaces.streams, "unsupported-feature") - - .. attribute:: UNSUPPORTED_STANZA_TYPE - :annotation: = (namespaces.streams, "unsupported-stanza-type") - - .. attribute:: UNSUPPORTED_VERSION - :annotation: = (namespaces.streams, "unsupported-version") - - """ - - BAD_FORMAT = (namespaces.streams, "bad-format") - BAD_NAMESPACE_PREFIX = (namespaces.streams, "bad-namespace-prefix") - CONFLICT = (namespaces.streams, "conflict") - CONNECTION_TIMEOUT = (namespaces.streams, "connection-timeout") - HOST_GONE = (namespaces.streams, "host-gone") - HOST_UNKNOWN = (namespaces.streams, "host-unknown") - IMPROPER_ADDRESSING = (namespaces.streams, "improper-addressing") - INTERNAL_SERVER_ERROR = (namespaces.streams, "internal-server-error") - INVALID_FROM = (namespaces.streams, "invalid-from") - INVALID_NAMESPACE = (namespaces.streams, "invalid-namespace") - INVALID_XML = (namespaces.streams, "invalid-xml") - NOT_AUTHORIZED = (namespaces.streams, "not-authorized") - NOT_WELL_FORMED = (namespaces.streams, "not-well-formed") - POLICY_VIOLATION = (namespaces.streams, "policy-violation") - REMOTE_CONNECTION_FAILED = (namespaces.streams, "remote-connection-failed") - RESET = (namespaces.streams, "reset") - RESOURCE_CONSTRAINT = (namespaces.streams, "resource-constraint") - RESTRICTED_XML = (namespaces.streams, "restricted-xml") - SEE_OTHER_HOST = (namespaces.streams, "see-other-host") - SYSTEM_SHUTDOWN = (namespaces.streams, "system-shutdown") - UNDEFINED_CONDITION = (namespaces.streams, "undefined-condition") - UNSUPPORTED_ENCODING = (namespaces.streams, "unsupported-encoding") - UNSUPPORTED_FEATURE = (namespaces.streams, "unsupported-feature") - UNSUPPORTED_STANZA_TYPE = (namespaces.streams, "unsupported-stanza-type") - UNSUPPORTED_VERSION = (namespaces.streams, "unsupported-version") - - -StreamErrorCondition.SEE_OTHER_HOST.xso_class.new_address = xso.Text() - - -class StreamError(ConnectionError): - def __init__(self, condition, text=None): - if not isinstance(condition, StreamErrorCondition): - condition = StreamErrorCondition(condition) - warnings.warn( - "as of aioxmpp 1.0, stream error conditions must be members " - "of the aioxmpp.errors.StreamErrorCondition enumeration", - DeprecationWarning, - stacklevel=2, - ) - - super().__init__("stream error: {}".format( - format_error_text(condition, text)) - ) - self.condition = condition - self.text = text - - -class StanzaError(Exception): - pass - - -class XMPPError(StanzaError): - """ - Exception representing an error defined in the XMPP protocol. - - :param condition: The :rfc:`6120` defined error condition as enumeration - member or :class:`aioxmpp.xso.XSO` - :type condition: :class:`aioxmpp.ErrorCondition` or - :class:`aioxmpp.xso.XSO` - :param text: Optional human-readable text explaining the error - :type text: :class:`str` - :param application_defined_condition: Object describing the error in more - detail - :type application_defined_condition: :class:`aioxmpp.xso.XSO` - - .. versionchanged:: 0.10 - - As of 0.10, `condition` should either be a - :class:`aioxmpp.ErrorCondition` enumeration member or an XSO - representing one of the error conditions. - - For compatibility, namespace-localpart tuples indicating the tag of - the defined error condition are still accepted. - - .. deprecated:: 0.10 - - Starting with aioxmpp 1.0, namespace-localpart tuples will not be - accepted anymore. See the changelog for notes on the transition. - - .. attribute:: condition_obj - - The :class:`aioxmpp.XSO` which represents the error condition. - - .. versionadded:: 0.10 - - .. autoattribute:: condition - - .. attribute:: text - - Optional human-readable text describing the error further. - - This is :data:`None` if the text is omitted. - - .. attribute:: application_defined_condition - - Optional :class:`aioxmpp.XSO` which further defines the error - condition. - - Relevant subclasses: - - .. autosummary:: - - aioxmpp.XMPPAuthError - aioxmpp.XMPPModifyError - aioxmpp.XMPPCancelError - aioxmpp.XMPPContinueError - aioxmpp.XMPPWaitError - - """ - - TYPE = structs.ErrorType.CANCEL - - def __init__(self, - condition, - text=None, - application_defined_condition=None): - if not isinstance(condition, (ErrorCondition, xso.XSO)): - condition = ErrorCondition(condition) - warnings.warn( - "as of aioxmpp 1.0, error conditions must be members of the " - "aioxmpp.ErrorCondition enumeration", - DeprecationWarning, - stacklevel=2, - ) - - super().__init__(format_error_text( - condition.enum_member, - text=text, - application_defined_condition=application_defined_condition)) - self.condition_obj = condition.to_xso() - self.text = text - self.application_defined_condition = application_defined_condition - - @property - def condition(self): - """ - :class:`aioxmpp.ErrorCondition` enumeration member representing the - error condition. - """ - - return self.condition_obj.enum_member - - -class XMPPWarning(XMPPError, UserWarning): - TYPE = structs.ErrorType.CONTINUE - - -class XMPPAuthError(XMPPError, PermissionError): - TYPE = structs.ErrorType.AUTH - - -class XMPPModifyError(XMPPError, ValueError): - TYPE = structs.ErrorType.MODIFY - - -class XMPPCancelError(XMPPError): - TYPE = structs.ErrorType.CANCEL - - -class XMPPWaitError(XMPPError): - TYPE = structs.ErrorType.WAIT - - -class XMPPContinueError(XMPPWarning): - TYPE = structs.ErrorType.CONTINUE - - -class ErroneousStanza(StanzaError): - """ - This exception is thrown into listeners for IQ responses by - :class:`aioxmpp.stream.StanzaStream` if a response for an IQ was received, - but could not be decoded (due to malformed or unsupported payload). - - .. attribute:: partial_obj - - Contains the partially decoded stanza XSO. Do not rely on any members - except those representing XML attributes (:attr:`~.StanzaBase.to`, - :attr:`~.StanzaBase.from_`, :attr:`~.StanzaBase.type_`). - - """ - - def __init__(self, partial_obj): - super().__init__("erroneous stanza received: {!r}".format( - partial_obj)) - self.partial_obj = partial_obj - - -class StreamNegotiationFailure(ConnectionError): - pass - - -class SecurityNegotiationFailure(StreamNegotiationFailure): - def __init__(self, xmpp_error, - kind="Security negotiation failure", - text=None): - msg = "{}: {}".format(kind, xmpp_error) - if text: - msg += " ('{}')".format(text) - super().__init__(msg) - self.xmpp_error = xmpp_error - self.text = text - - -class SASLUnavailable(SecurityNegotiationFailure): - # we use this to tell the Client that SASL has not been available at all, - # or that we could not agree on mechanisms. - # it might be helpful to notify the peer about this before dying. - pass - - -class TLSFailure(SecurityNegotiationFailure): - def __init__(self, xmpp_error, text=None): - super().__init__(xmpp_error, text=text, kind="TLS failure") - - -class TLSUnavailable(TLSFailure): - pass - - -class UserError(Exception): - """ - An exception subclass, which should be used as a mix-in. - - It is intended to be used for exceptions which may be user-facing, such as - connection errors, value validation issues and the like. - - `localizable_string` must be a :class:`.i18n.LocalizableString` - instance. The `args` and `kwargs` will be passed to - :class:`.LocalizableString.localize` when either :func:`str` is called on - the :class:`UserError` or :meth:`localize` is called. - - The :func:`str` is created using the default - :class:`~.i18n.LocalizingFormatter` and a :class:`gettext.NullTranslations` - instance. The point in time at which the default localizing formatter is - created is unspecified. - - .. automethod:: localize - - """ - - DEFAULT_FORMATTER = i18n.LocalizingFormatter() - DEFAULT_TRANSLATIONS = gettext.NullTranslations() - - def __init__(self, localizable_string, *args, **kwargs): - super().__init__() - self._str = localizable_string.localize( - self.DEFAULT_FORMATTER, - self.DEFAULT_TRANSLATIONS, - *args, **kwargs) - self.localizable_string = localizable_string - self.args = args - self.kwargs = kwargs - - def __str__(self): - return str(self._str) - - def localize(self, formatter, translator): - """ - Return a localized version of the `localizable_string` passed to the - constructor. It is formatted using the `formatter` with the `args` and - `kwargs` passed to the constructor of :class:`UserError`. - """ - return self.localizable_string.localize( - formatter, - translator, - *self.args, - **self.kwargs - ) - - -class UserValueError(UserError, ValueError): - """ - This is a :class:`ValueError` with :class:`UserError` mixed in. - """ - - -class MultiOSError(OSError): - """ - Describe an error situation which has been caused by the sequential - occurrence of multiple other `exceptions`. - - The `message` shall be descriptive and will be prepended to a concatenation - of the error messages of the given `exceptions`. - """ - - def __init__(self, message, exceptions): - flattened_exceptions = [] - for exc in exceptions: - if hasattr(exc, "exceptions"): - flattened_exceptions.extend(exc.exceptions) - else: - flattened_exceptions.append(exc) - - super().__init__( - "{}: multiple errors: {}".format( - message, - ", ".join(map(str, flattened_exceptions)) - ) - ) - self.exceptions = flattened_exceptions - - -class GatherError(RuntimeError): - """ - Describe an error situation which has been caused by the occurrence - of multiple other `exceptions`. - - The `message` shall be descriptive and will be prepended to a concatenation - of the error messages of the given `exceptions`. - """ - - def __init__(self, message, exceptions): - flattened_exceptions = [] - for exc in exceptions: - if hasattr(exc, "exceptions"): - flattened_exceptions.extend(exc.exceptions) - else: - flattened_exceptions.append(exc) - - super().__init__( - "{}: multiple errors: {}".format( - message, - ", ".join(map(str, flattened_exceptions)) - ) - ) - self.exceptions = flattened_exceptions diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__init__.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__init__.py deleted file mode 100644 index 4b2f445..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__init__.py +++ /dev/null @@ -1,201 +0,0 @@ -######################################################################## -# File name: __init__.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -""" -:mod:`~aioxmpp.forms` --- Data Forms support (:xep:`4`) -####################################################### - -This subpackage contains tools to deal with :xep:`4` Data Forms. Data Forms is -a pervasive and highly flexible protocol used in XMPP. It allows for -machine-readable (and processable) forms as well as tables of data. This -flexibility comes unfortunately at the price of complexity. This subpackage -attempts to take some of the load of processing Data Forms off the application -developer. - -Cheat Sheet: - -* The :class:`Form` class exists for use cases where automated processing of - Data Forms is supposed to happen. The - :ref:`api-aioxmpp.forms-declarative-style` allow convenient access to and - manipulation of form data from within code. - -* Direct use of the :class:`Data` XSO is advisable if you want to present forms - or data to sentient beings: even though :class:`Form` is more convenient for - machine-to-machine use, using the :class:`Data` sent by the peer easily - allows showing the user *all* fields supported by the peer. - -* For machine-processed tables, there is no tooling (yet). - -.. versionadded:: 0.7 - - Even though the :mod:`aioxmpp.forms` module existed pre-0.7, it has not been - documented and was thus not part of the public API. - -.. note:: - - The authors are not entirely happy with the API at some points. - Specifically, at some places where mutable data structures are used, the - mutation of these data structures may have unexpected side effects. This may - be rectified in a future release by replacing these data structures with - their appropriate immutable equivalents. - - These locations are marked accordingly. - -Attributes added to stanzas -=========================== - -:mod:`aioxmpp.forms` adds the following attributes to stanzas: - -.. attribute:: aioxmpp.Message.xep0004_data - - A sequence of :class:`Data` instances. This is used for example by the - :mod:`~.muc` implementation (:xep:`45`). - - .. versionadded:: 0.8 - - -.. _api-aioxmpp.forms-declarative-style: - -Declarative-style Forms -======================= - -Base class ----------- - -.. autoclass:: Form - -Fields ------- - -Text fields -~~~~~~~~~~~ - -.. autoclass:: TextSingle(var, type_=xso.String(), *[, default=None][, required=False][, desc=None][, label=None]) - -.. autoclass:: TextPrivate(var, type_=xso.String(), *[, default=None][, required=False][, desc=None][, label=None]) - -.. autoclass:: TextMulti(var, type_=xso.String(), *[, default=()][, required=False][, desc=None][, label=None]) - -JID fields -~~~~~~~~~~ - -.. autoclass:: JIDSingle(var, *[, default=None][, required=False][, desc=None][, label=None]) - -.. autoclass:: JIDMulti(var, *[, default=()][, required=False][, desc=None][, label=None]) - -Selection fields -~~~~~~~~~~~~~~~~ - -.. autoclass:: ListSingle(var, type_=xso.String(), *[, default=None][, options=[]][, required=False][, desc=None][, label=None]) - -.. autoclass:: ListMulti(var, type_=xso.String(), *[, default=frozenset()][, options=[]][, required=False][, desc=None][, label=None]) - -Other fields -~~~~~~~~~~~~ - -.. autoclass:: Boolean(var, *[, default=False][, required=False][, desc=None][, label=None]) - -Abstract base classes -~~~~~~~~~~~~~~~~~~~~~ - -.. currentmodule:: aioxmpp.forms.fields - -.. autoclass:: AbstractField - -.. autoclass:: AbstractChoiceField(var, type_=xso.String(), *[, options=[]][, required=False][, desc=None][, label=None]) - -.. currentmodule:: aioxmpp.forms - -.. _api-aioxmpp.forms-bound-fields: - -Bound fields -============ - -Bound fields are objects which are returned when the descriptor attribute is -accessed on a form instance. It holds the value of the field, as well as -overrides for the default (specified on the descriptors themselves) values for -certain attributes (such as :attr:`~.AbstractField.desc`). - -For the different field types, there are different classes of bound fields, -which are documented below. - -.. currentmodule:: aioxmpp.forms.fields - -.. autoclass:: BoundField - -.. autoclass:: BoundSingleValueField - -.. autoclass:: BoundMultiValueField - -.. autoclass:: BoundOptionsField - -.. autoclass:: BoundSelectField - -.. autoclass:: BoundMultiSelectField - -.. currentmodule:: aioxmpp.forms - -XSOs -==== - -.. autoclass:: Data - -.. autoclass:: DataType - -.. autoclass:: Field - -.. autoclass:: FieldType - -Report and table support ------------------------- - -.. autoclass:: Reported - -.. autoclass:: Item - -""" # NOQA: E501 - - -from . import xso # NOQA: F401 - -from .xso import ( # NOQA: F401 - Data, - DataType, - Field, - FieldType, - Reported, - Item, -) - -from .fields import ( # NOQA: F401 - Boolean, - ListSingle, - ListMulti, - JIDSingle, - JIDMulti, - TextSingle, - TextMulti, - TextPrivate, -) - -from .form import ( # NOQA: F401 - Form, -) diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/__init__.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 36c1dab..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/fields.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/fields.cpython-311.pyc deleted file mode 100644 index bf8d18e..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/fields.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/form.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/form.cpython-311.pyc deleted file mode 100644 index 8e25058..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/form.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/xso.cpython-311.pyc b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/xso.cpython-311.pyc deleted file mode 100644 index 26010f0..0000000 Binary files a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/__pycache__/xso.cpython-311.pyc and /dev/null differ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/fields.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/fields.py deleted file mode 100644 index c78f1fd..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/fields.py +++ /dev/null @@ -1,1201 +0,0 @@ -######################################################################## -# File name: fields.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import abc -import collections -import copy - -import aioxmpp.xso as xso - -from . import xso as forms_xso - - -descriptor_ns = "{jabber:x:data}field" - - -FIELD_DOCSTRING_TEMPLATE = """ -``{field_type.value}`` field ``{var}`` - -{label} -""" - - -class BoundField(metaclass=abc.ABCMeta): - """ - Abstract base class for objects returned by the field descriptors. - - :param field: field descriptor to bind - :type field: :class:`AbstractField` - :param instance: form instance to bind to - :type instance: :class:`object` - - :class:`BoundField` instances represent the connection between the field - descriptors present at a form *class* and the *instance* of that class. - - They store of course the value of the field for the specific instance, but - also possible instance-specific overrides for the metadata attributes - :attr:`desc`, :attr:`label` and :attr:`required` (and possibly - :attr:`.BoundOptionsField.options`). By default, these attributes return - the same value as set on the corresponding `field`, but the attributes can - be set to different values, which only affects the single form instance. - - The use case is to fill these fields with the information obtained from a - :class:`.Data` XSO when creating a form with :meth:`.Form.from_xso`: it - allows the fields to behave exactly like the sender specified. - - Deep-copying a :class:`BoundField` deepcopies all attributes, except the - :attr:`field` and :attr:`instance` attributes. See also :meth:`clone_for` - for copying a bound field for a new `instance`. - - Subclass overview: - - .. autosummary:: - - BoundSingleValueField - BoundMultiValueField - BoundSelectField - BoundMultiSelectField - - Binding relationship: - - .. autoattribute:: field - - .. attribute:: instance - - The `instance` as passed to the constructor. - - Field metadata attributes: - - .. autoattribute:: desc - - .. autoattribute:: label - - .. autoattribute:: required - - Helper methods: - - .. automethod:: clone_for - - The following methods must be implemented by base classes. - - .. automethod:: load - - .. automethod:: render - - """ - - def __init__(self, field, instance): - super().__init__() - self._field = field - self.instance = instance - - @property - def field(self): - """ - The field which is bound to the :attr:`instance`. - """ - return self._field - - def __repr__(self): - return "".format( - self._field, - self.instance, - id(self), - ) - - def __deepcopy__(self, memo): - result = copy.copy(self) - for k, v in self.__dict__.items(): - if k == "_field" or k == "instance": - continue - setattr(result, k, copy.deepcopy(v, memo)) - return result - - @property - def desc(self): - """ - .. seealso:: - - :attr:`.Field.desc` - for a full description of the ``desc`` semantics. - """ - - try: - return self._desc - except AttributeError: - return self._field.desc - - @desc.setter - def desc(self, value): - self._desc = value - - @property - def label(self): - """ - .. seealso:: - - :attr:`.Field.label` - for a full description of the ``label`` semantics. - """ - try: - return self._label - except AttributeError: - return self._field.label - - @label.setter - def label(self, value): - self._label = value - - @property - def required(self): - """ - .. seealso:: - - :attr:`.Field.required` - for a full description of the ``required`` semantics. - """ - try: - return self._required - except AttributeError: - return self._field.required - - @required.setter - def required(self, value): - self._required = value - - def clone_for(self, other_instance, memo=None): - """ - Clone this bound field for another instance, possibly during a - :func:`~copy.deepcopy` operation. - - :param other_instance: Another form instance to which the newly created - bound field shall be bound. - :type other_instance: :class:`object` - :param memo: Optional deepcopy-memo (see :mod:`copy` for details) - - If this is called during a deepcopy operation, passing the `memo` helps - preserving and preventing loops. This method is essentially a - deepcopy-operation, with a modification of the :attr:`instance` - afterwards. - """ - - if memo is None: - result = copy.deepcopy(self) - else: - result = copy.deepcopy(self, memo) - result.instance = other_instance - return result - - @abc.abstractmethod - def load(self, field_xso): - """ - Load the field information from a data field. - - :param field_xso: XSO describing the field. - :type field_xso: :class:`~.Field` - - This loads the current value, description, label and possibly options - from the `field_xso`, shadowing the information from the declaration of - the field on the class. - - This method is must be overridden and is thus marked abstract. However, - when called from a subclass, it loads the :attr:`desc`, :attr:`label` - and :attr:`required` from the given `field_xso`. Subclasses are - supposed to implement a mechanism to load options and/or values from - the `field_xso` and then call this implementation through - :func:`super`. - """ - if field_xso.desc: - self._desc = field_xso.desc - - if field_xso.label: - self._label = field_xso.label - - self._required = field_xso.required - - @abc.abstractmethod - def render(self, *, use_local_metadata=True): - """ - Return a :class:`~.Field` containing the values and metadata set in the - field. - - :param use_local_metadata: if true, the description, label and required - metadata can be sourced from the field - descriptor associated with this bound field. - :type use_local_metadata: :class:`bool` - :return: A new :class:`~.Field` instance. - - The returned object uses the values accessible through this object; - that means, any values set for e.g. :attr:`desc` take precedence over - the values declared at the class level. If `use_local_metadata` is - false, values declared at the class level are not used if no local - values are declared. This is useful when generating a reply to a form - received by a peer, as it avoids sending a modified form. - - This method is must be overridden and is thus marked abstract. However, - when called from a subclass, it creates the :class:`~.Field` instance - and initialises its :attr:`~.Field.var`, :attr:`~.Field.type_`, - :attr:`~.Field.desc`, :attr:`~.Field.required` and - :attr:`~.Field.label` attributes and returns the result. Subclasses are - supposed to override this method, call the base implementation through - :func:`super` to obtain the :class:`~.Field` instance and then fill in - the values and/or options. - """ - - result = forms_xso.Field( - var=self.field.var, - type_=self.field.FIELD_TYPE, - ) - - if use_local_metadata: - result.desc = self.desc - result.label = self.label - result.required = self.required - else: - try: - result.desc = self._desc - except AttributeError: - pass - - try: - result.label = self._label - except AttributeError: - pass - - try: - result.required = self._required - except AttributeError: - pass - - return result - - -class BoundSingleValueField(BoundField): - """ - A bound field which has only a single value at any time. Only the first - value is parsed when loading data from a :class:`~.Field` XSO. When writing - data to a :class:`~.Field` XSO, :data:`None` is treated as the absence of - any value; every other value is serialised through the - :attr:`~.AbstractField.type_` of the field. - - .. seealso:: - - :class:`BoundField` - for a description of the arguments. - - This bound field is used by :class:`TextSingle`, :class:`TextPrivate` and - :class:`JIDSingle`. - - .. autoattribute:: value - """ - - @property - def value(self): - """ - The current value of the field. If no value is set when this attribute - is accessed for reading, the :meth:`default` of the field is invoked - and the result is set and returned as value. - - Only values which pass through :meth:`~.AbstractCDataType.coerce` of - the :attr:`~.AbstractField.type_` of the field can be set. To - revert the :attr:`value` to its default, use the ``del`` operator. - """ - try: - return self._value - except AttributeError: - # call through to field - self._value = self._field.default() - return self._value - - @value.setter - def value(self, value): - self._value = self._field.type_.coerce(value) - - @value.deleter - def value(self): - try: - del self._value - except AttributeError: - pass - - def load(self, field_xso): - try: - value = field_xso.values[0] - except IndexError: - value = self._field.default() - else: - value = self._field.type_.parse(value) - - self._value = value - - super().load(field_xso) - - def render(self, **kwargs): - result = super().render(**kwargs) - - try: - value = self._value - except AttributeError: - value = self._field.default() - - if value is None: - return result - - result.values[:] = [ - self.field.type_.format(value) - ] - - return result - - -class BoundMultiValueField(BoundField): - """ - A bound field which can have multiple values. - - .. seealso:: - - :class:`BoundField` - for a description of the arguments. - - This bound field is used by :class:`TextMulti` and :class:`JIDMulti`. - - .. autoattribute:: value - """ - - @property - def value(self): - """ - A tuple of values. This attribute can be set with any iterable; the - iterable is then evaluated into a tuple and stored at the bound field. - - Whenever values are written to this attribute, they are passed through - the :meth:`~.AbstractCDataType.coerce` method of the - :attr:`~.AbstractField.type_` of the field. To revert the - :attr:`value` to its default, use the ``del`` operator. - """ - try: - return self._value - except AttributeError: - self.value = self._field.default() - return self._value - - @value.setter - def value(self, values): - coerce = self._field.type_.coerce - self._value = tuple( - coerce(v) - for v in values - ) - - @value.deleter - def value(self): - try: - del self._value - except AttributeError: - pass - - def load(self, field_xso): - self._value = tuple( - self._field.type_.parse(v) - for v in field_xso.values - ) - - super().load(field_xso) - - def render(self, **kwargs): - result = super().render(**kwargs) - result.values[:] = ( - self.field.type_.format(v) - for v in self._value - ) - return result - - -class BoundOptionsField(BoundField): - """ - This is an intermediate base class used to implement bound fields for - fields which have options from which one or more values must be chosen. - - .. seealso:: - - :class:`BoundField` - for a description of the arguments. - - When the field is loaded from a :class:`~.Field` XSO, the options are also - loaded from there and thus shadow the options defined at the `field`. This - may come to a surprise of code expecting a specific set of options. - - Subclass overview: - - .. autosummary:: - - BoundSelectField - BoundMultiSelectField - - .. autoattribute:: options - """ - - @property - def options(self): - """ - This is a :class:`collections.OrderedDict` which maps option keys to - their labels. The keys are used as the values of the field; the labels - are human-readable text for display. - - This attribute can be written with any object which is compatible with - the dict-constructor. The order is preserved if a sequence of key-value - pairs is used. - - When writing the attribute, the keys are checked against the - :meth:`~.AbstractCDataType.coerce` method of the - :attr:`~.AbstractField.type_` of the field. To make the :attr:`options` - attribute identical to the :attr:`~.AbstractField.options` attribute, - use the ``del`` operator. - - .. warning:: - - This attribute is mutable, however, mutating it directly may have - unexpected side effects: - - * If the attribute has not been set before, you will actually be - mutating the :class:`~.AbstractChoiceField.options` attributes - value. - - This may be changed in the future by copying more eagerly. - - * The type checking cannot take place when keys are added by direct - mutation of the dictionary. This means that errors will be delayed - until the actual serialisation of the data form, which may be a - confusing thing to debug. - - Relying on the above behaviour or any other behaviour induced by - directly mutating the value returned by this attribute is **not - recommended**. Changes to this behaviour are *not* considered - breaking changes and will be done without the usual deprecation. - """ - try: - return self._options - except AttributeError: - return self.field.options - - @options.setter - def options(self, value): - iterator = (value.items() - if isinstance(value, collections.abc.Mapping) - else value) - self._options = collections.OrderedDict( - (self.field.type_.coerce(k), v) - for k, v in iterator - ) - - @options.deleter - def options(self): - try: - del self._options - except AttributeError: - pass - - def load(self, field_xso): - self._options = collections.OrderedDict( - field_xso.options - ) - super().load(field_xso) - - def render(self, **kwargs): - format_ = self._field.type_.format - field_xso = super().render(**kwargs) - field_xso.options.update( - (format_(k), v) - for k, v in self.options.items() - ) - return field_xso - - -class BoundSelectField(BoundOptionsField): - """ - Bound field carrying one value out of a set of options. - - .. seealso:: - - :class:`BoundField` - for a description of the arguments. - :attr:`BoundOptionsField.options` - for semantics and behaviour of the ``options`` attribute - - .. autoattribute:: value - - """ - - @property - def value(self): - """ - The current value of the field. If no value is set when this attribute - is accessed for reading, the :meth:`default` of the field is invoked - and the result is set and returned as value. - - Only values contained in the :attr:`~.BoundOptionsField.options` can be - set, other values are rejected with a :class:`ValueError`. To revert - the value to the default value specified in the descriptor, use the - ``del`` operator. - """ - try: - return self._value - except AttributeError: - self._value = self.field.default() - return self._value - - @value.setter - def value(self, value): - options = self.options - if value not in options: - raise ValueError("{!r} not in field options: {!r}".format( - value, - tuple(options.keys()), - )) - - self._value = value - - @value.deleter - def value(self): - try: - del self._value - except AttributeError: - pass - - def load(self, field_xso): - try: - value = field_xso.values[0] - except IndexError: - try: - del self._value - except AttributeError: - pass - else: - self._value = self.field.type_.parse(value) - - super().load(field_xso) - - def render(self, **kwargs): - format_ = self._field.type_.format - field_xso = super().render(**kwargs) - value = self.value - if value is not None: - field_xso.values[:] = [format_(value)] - return field_xso - - -class BoundMultiSelectField(BoundOptionsField): - """ - Bound field carrying a subset of values out of a set of options. - - .. seealso:: - - :class:`BoundField` - for a description of the arguments. - :attr:`BoundOptionsField.options` - for semantics and behaviour of the ``options`` attribute - - .. autoattribute:: value - """ - - @property - def value(self): - """ - A :class:`frozenset` whose elements are a subset of the keys of the - :attr:`~.BoundOptionsField.options` mapping. - - This value can be written with any iterable; the iterable is then - evaluated into a :class:`frozenset`. If it contains any value not - contained in the set of keys of options, the attribute is not written - and :class:`ValueError` is raised. - - To revert the value to the default specified by the field descriptor, - use the ``del`` operator. - """ - try: - return self._value - except AttributeError: - self.value = self.field.default() - return self._value - - @value.setter - def value(self, values): - new_values = frozenset(values) - options = set(self.options.keys()) - invalid = new_values - options - if invalid: - raise ValueError( - "{!r} not in field options: {!r}".format( - next(iter(invalid)), - tuple(self.options.keys()) - ) - ) - - self._value = new_values - - @value.deleter - def value(self): - try: - del self._value - except AttributeError: - pass - - def load(self, field_xso): - self._value = frozenset( - self.field.type_.parse(value) - for value in field_xso.values - ) - super().load(field_xso) - - def render(self, **kwargs): - format_ = self.field.type_.format - - result = super().render(**kwargs) - result.values[:] = [ - format_(value) - for value in self.value - ] - return result - - -class AbstractDescriptor(metaclass=abc.ABCMeta): - attribute_name = None - root_class = None - - @abc.abstractmethod - def descriptor_keys(self): - """ - Return an iterator with the descriptor keys for this descriptor. The - keys will be added to the :attr:`DescriptorClass.DESCRIPTOR_KEYS` - mapping, pointing to the descriptor. - - Duplicate keys will lead to a :class:`TypeError` being raised during - declaration of the class. - """ - - -class AbstractField(AbstractDescriptor): - """ - Abstract base class to implement field descriptor classes. - - :param var: The field ``var`` attribute this descriptor is supposed to - represent. - :type var: :class:`str` - :param type_: The type of the data, defaults to :class:`~.xso.String`. - :type type_: :class:`~.xso.AbstractCDataType` - :param required: Flag to indicate that the field is required. - :type required: :class:`bool` - :param desc: Description text for the field, e.g. for tool-tips. - :type desc: :class:`str`, without newlines - :param label: Short, human-readable label for the field - :type label: :class:`str` - - The arguments are used to initialise the respective attributes. Details on - the semantics can be found in the respective documentation pieces below. - - .. autoattribute:: desc - - .. attribute:: label - - Represents the label flag as specified per :xep:`4`. - - The value of this attribute is used when forms are generated locally. - When forms are received from remote peers and :class:`~.Form` instances - are constructed from that data, this attribute is not used when - rendering a reply or when the value is accessed through the bound field. - - .. seealso:: - - :attr:`~.Field.label` - for details on the semantics of this attribute - - .. attribute:: required - - Represents the required flag as specified per :xep:`4`. - - The value of this attribute is used when forms are generated locally. - When forms are received from remote peers and :class:`~.Form` instances - are constructed from that data, this attribute is not used when - rendering a reply or when the value is accessed through the bound field. - - .. seealso:: - - :attr:`~.Field.required` - for details on the semantics of this attribute - - .. autoattribute:: var - - .. automethod:: create_bound - - .. automethod:: default - - .. automethod:: make_bound - """ - - def __init__(self, var, type_, *, required=False, desc=None, label=None): - super().__init__() - self._var = var - self.required = required - self.desc = desc - self.label = label - self._type = type_ - self.__doc__ = FIELD_DOCSTRING_TEMPLATE.format( - field_type=self.FIELD_TYPE, - var=self.var, - desc=self.desc, - label=self.label, - ) - - def descriptor_keys(self): - yield descriptor_ns, self._var - - @property - def type_(self): - """ - :class:`.AbstractCDataType` instance used to parse, validate and - format the value(s) of this field. - - The type of a field cannot be changed after its initialisation. - """ - return self._type - - @property - def desc(self): - """ - Represents the description as specified per :xep:`4`. - - The value of this attribute is used when forms are generated locally. - When forms are received from remote peers and :class:`~.Form` instances - are constructed from that data, this attribute is not used when - rendering a reply or when the value is accessed through the bound - field. - - .. seealso:: - - :attr:`~.Field.desc` - for details on the semantics of this attribute - """ - - return self._desc - - @desc.setter - def desc(self, value): - if value is not None and any(ch == "\r" or ch == "\n" for ch in value): - raise ValueError("desc must not contain newlines") - self._desc = value - - @desc.deleter - def desc(self): - self._desc = None - - @property - def var(self): - """ - Represents the field ID as specified per :xep:`4`. - - The value of this attribute is used to match fields when instantiating - :class:`~.Form` classes from :class:`~.Data` XSOs. - - .. seealso:: - - :attr:`~.Field.var` - for details on the semantics of this attribute - """ - return self._var - - @abc.abstractmethod - def default(self): - """ - Create and return a default value for this field. - - This must be implemented by subclasses. - """ - - @abc.abstractmethod - def create_bound(self, for_instance): - """ - Create a :ref:`bound field class ` - instance for this field for the given form object and return it. - - :param for_instance: The form instance to which the bound field should - be bound. - - This method must be re-implemented by subclasses. - - .. seealso:: - - :meth:`make_bound` - creates (using this method) or returns an existing bound field - for a given form instance. - - """ - - def make_bound(self, for_instance): - """ - Create a new :ref:`bound field class ` - or return an existing one for the given form object. - - :param for_instance: The form instance to which the bound field should - be bound. - - If no bound field can be found on the given `for_instance` for this - field, a new one is created using :meth:`create_bound`, stored at the - instance and returned. Otherwise, the existing instance is returned. - - .. seealso:: - - :meth:`create_bound` - creates a new bound field for the given form instance (without - storing it anywhere). - """ - - try: - return for_instance._descriptor_data[self] - except KeyError: - bound = self.create_bound(for_instance) - for_instance._descriptor_data[self] = bound - return bound - - def __get__(self, instance, type_): - if instance is None: - return self - return self.make_bound(instance) - - -class TextSingle(AbstractField): - """ - Represent a ``"text-single"`` input with the given `var`. - - :param default: A default value to initialise the field. - - .. seealso:: - - :class:`~.fields.BoundSingleValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `type_`, `required`, `desc` and - `label` arguments. - - """ - FIELD_TYPE = forms_xso.FieldType.TEXT_SINGLE - - def __init__(self, var, type_=xso.String(), *, - default=None, - **kwargs): - super().__init__(var, type_, **kwargs) - self._default = default - - def default(self): - return self._default - - def create_bound(self, for_instance): - return BoundSingleValueField( - self, - for_instance, - ) - - -class JIDSingle(AbstractField): - """ - Represent a ``"jid-single"`` input with the given `var`. - - :param default: A default value to initialise the field. - - .. seealso:: - - :class:`~.fields.BoundSingleValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `required`, `desc` and - `label` arguments. - - """ - FIELD_TYPE = forms_xso.FieldType.JID_SINGLE - - def __init__(self, var, *, default=None, **kwargs): - super().__init__(var, type_=xso.JID(), **kwargs) - self._default = default - - def default(self): - return self._default - - def create_bound(self, for_instance): - return BoundSingleValueField( - self, - for_instance, - ) - - -class Boolean(AbstractField): - """ - Represent a ``"boolean"`` input with the given `var`. - - :param default: A default value to initialise the field. - - .. seealso:: - - :class:`~.fields.BoundSingleValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `required`, `desc` and - `label` arguments. - - """ - - FIELD_TYPE = forms_xso.FieldType.BOOLEAN - - def __init__(self, var, *, default=False, **kwargs): - super().__init__(var, xso.Bool(), **kwargs) - self._default = default - - def default(self): - return self._default - - def create_bound(self, for_instance): - return BoundSingleValueField( - self, - for_instance, - ) - - -class TextPrivate(TextSingle): - """ - Represent a ``"text-private"`` input with the given `var`. - - :param default: A default value to initialise the field. - - .. seealso:: - - :class:`~.fields.BoundSingleValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `type_`, `required`, `desc` and - `label` arguments. - - """ - - FIELD_TYPE = forms_xso.FieldType.TEXT_PRIVATE - - -class TextMulti(AbstractField): - """ - Represent a ``"text-multi"`` input with the given `var`. - - :param default: A default value to initialise the field. - :type default: :class:`tuple` - - .. seealso:: - - :class:`~.fields.BoundMultiValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `type_`, `required`, `desc` and - `label` arguments. - - """ - - FIELD_TYPE = forms_xso.FieldType.TEXT_MULTI - - def __init__(self, var, type_=xso.String(), *, - default=(), **kwargs): - super().__init__(var, type_, **kwargs) - self._default = default - - def create_bound(self, for_instance): - return BoundMultiValueField(self, for_instance) - - def default(self): - return self._default - - -class JIDMulti(AbstractField): - """ - Represent a ``"jid-multi"`` input with the given `var`. - - :param default: A default value to initialise the field. - :type default: :class:`tuple` - - .. seealso:: - - :class:`~.fields.BoundMultiValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `type_`, `required`, `desc` and - `label` arguments. - - """ - - FIELD_TYPE = forms_xso.FieldType.JID_MULTI - - def __init__(self, var, *, default=(), **kwargs): - super().__init__(var, xso.JID(), **kwargs) - self._default = default - - def create_bound(self, for_instance): - return BoundMultiValueField(self, for_instance) - - def default(self): - return self._default - - -class AbstractChoiceField(AbstractField): - """ - Abstract base class to implement field descriptor classes using options. - - :param type_: Type used for the option keys. - :type type_: :class:`~.xso.AbstractCDataType` - :param options: A sequence of key-value pairs or a mapping object - representing the options available. - :type options: sequence of pairs or mapping - - The keys of the `options` mapping (or the first elements in the pairs in - the sequence of pairs) must be compatible with `type_`, in the sense that - must pass through :meth:`~.xso.AbstractCDataType.coerce` (this is enforced - when the field is instantiated). - - Fields using this base class: - - .. autosummary:: - - aioxmpp.forms.ListSingle - aioxmpp.forms.ListMulti - - .. seealso:: - - :class:`~.fields.BoundOptionsField` - is an abstract base class to implement :ref:`bound field classes - ` for fields inheriting from this - class. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `required`, `desc` and `label` - arguments. - - """ - - def __init__(self, var, *, - type_=xso.String(), - options=[], - **kwargs): - super().__init__(var, type_, **kwargs) - iterator = (options.items() - if isinstance(options, collections.abc.Mapping) - else options) - self.options = collections.OrderedDict( - (type_.coerce(k), v) - for k, v in iterator - ) - self._type = type_ - - @property - def type_(self): - return self._type - - -class ListSingle(AbstractChoiceField): - """ - Represent a ``"list-single"`` input with the given `var`. - - :param default: A default value to initialise the field. This must be a - member of the `options`. - - .. seealso:: - - :class:`~.fields.BoundMultiValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractChoiceField` - for documentation on the `options` argument. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `type_`, `required`, `desc` and - `label` arguments. - - """ - - FIELD_TYPE = forms_xso.FieldType.LIST_SINGLE - - def __init__(self, var, *, default=None, **kwargs): - super().__init__(var, **kwargs) - if default is not None and default not in self.options: - raise ValueError("invalid default: not in options") - self._default = default - - def create_bound(self, for_instance): - return BoundSelectField(self, for_instance) - - def default(self): - return self._default - - -class ListMulti(AbstractChoiceField): - """ - Represent a ``"list-multi"`` input with the given `var`. - - :param default: An iterable of `options` keys - :type default: iterable - - `default` is evaluated into a :class:`frozenset` and all elements must be - keys of the `options` mapping argument. - - .. seealso:: - - :class:`~.fields.BoundMultiValueField` - is the :ref:`bound field class ` used - by fields of this type. - - :class:`~.fields.AbstractChoiceField` - for documentation on the `options` argument. - - :class:`~.fields.AbstractField` - for documentation on the `var`, `type_`, `required`, `desc` and - `label` arguments. - - """ - - FIELD_TYPE = forms_xso.FieldType.LIST_MULTI - - def __init__(self, var, *, default=frozenset(), **kwargs): - super().__init__(var, **kwargs) - self._default = frozenset(default) - if any(value not in self.options for value in self._default): - raise ValueError( - "invalid default: not in options" - ) - - def create_bound(self, for_instance): - return BoundMultiSelectField(self, for_instance) - - def default(self): - return self._default diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/form.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/form.py deleted file mode 100644 index 20c44e8..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/form.py +++ /dev/null @@ -1,476 +0,0 @@ -######################################################################## -# File name: form.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import abc -import copy - -from . import xso as forms_xso -from . import fields as fields - - -def descriptor_attr_name(descriptor): - return "_descriptor_{:x}".format(id(descriptor)) - - -class DescriptorClass(abc.ABCMeta): - @classmethod - def _merge_descriptors(mcls, dest_map, source): - for key, (descriptor, from_class) in source: - try: - existing_descriptor, exists_at_class = dest_map[key] - except KeyError: - pass - else: - if descriptor is not existing_descriptor: - raise TypeError( - "descriptor with key {!r} already " - "declared at {}".format( - key, - exists_at_class, - ) - ) - else: - continue - - dest_map[key] = descriptor, from_class - - @classmethod - def _upcast_descriptor_map(mcls, descriptor_map, from_class): - return { - key: (descriptor, from_class) - for key, descriptor in descriptor_map.items() - } - - def __new__(mcls, name, bases, namespace, *, protect=True): - descriptor_info = {} - - for base in bases: - if not isinstance(base, DescriptorClass): - continue - - base_descriptor_info = mcls._upcast_descriptor_map( - base.DESCRIPTOR_MAP, - "{}.{}".format( - base.__module__, - base.__qualname__, - ) - ) - mcls._merge_descriptors( - descriptor_info, - base_descriptor_info.items(), - ) - - fqcn = "{}.{}".format( - namespace["__module__"], - namespace["__qualname__"], - ) - - descriptors = [ - (attribute_name, descriptor) - for attribute_name, descriptor in namespace.items() - if isinstance(descriptor, fields.AbstractDescriptor) - ] - - if any(descriptor.root_class is not None - for _, descriptor in descriptors): - raise ValueError( - "descriptor cannot be used on multiple classes" - ) - - mcls._merge_descriptors( - descriptor_info, - ( - (key, (descriptor, fqcn)) - for _, descriptor in descriptors - for key in descriptor.descriptor_keys() - ) - ) - - namespace["DESCRIPTOR_MAP"] = { - key: descriptor - for key, (descriptor, _) in descriptor_info.items() - } - namespace["DESCRIPTORS"] = set(namespace["DESCRIPTOR_MAP"].values()) - if "__slots__" not in namespace and protect: - namespace["__slots__"] = () - - result = super().__new__(mcls, name, bases, namespace) - - for attribute_name, descriptor in descriptors: - descriptor.attribute_name = attribute_name - descriptor.root_class = result - - return result - - def __init__(self, name, bases, namespace, *, protect=True): - super().__init__(name, bases, namespace) - - def _is_descriptor_attribute(self, name): - try: - existing = getattr(self, name) - except AttributeError: - pass - else: - if isinstance(existing, fields.AbstractDescriptor): - return True - return False - - def __setattr__(self, name, value): - if self._is_descriptor_attribute(name): - raise AttributeError("descriptor attributes cannot be set") - - if not isinstance(value, fields.AbstractDescriptor): - return super().__setattr__(name, value) - - if self.__subclasses__(): - raise TypeError("cannot add descriptors to classes with " - "subclasses") - - meta = type(self) - descriptor_info = meta._upcast_descriptor_map( - self.DESCRIPTOR_MAP, - "{}.{}".format(self.__module__, self.__qualname__), - ) - - new_descriptor_info = [ - (key, (value, "")) - for key in value.descriptor_keys() - ] - - # this would raise on conflict - meta._merge_descriptors( - descriptor_info, - new_descriptor_info, - ) - - for key, (descriptor, _) in new_descriptor_info: - self.DESCRIPTOR_MAP[key] = descriptor - - self.DESCRIPTORS.add(value) - - return super().__setattr__(name, value) - - def __delattr__(self, name): - if self._is_descriptor_attribute(name): - raise AttributeError("removal of descriptors is not allowed") - - return super().__delattr__(name) - - def _register_descriptor_keys(self, descriptor, keys): - """ - Register the given descriptor keys for the given descriptor at the - class. - - :param descriptor: The descriptor for which the `keys` shall be - registered. - :type descriptor: :class:`AbstractDescriptor` instance - :param keys: An iterable of descriptor keys - :raises TypeError: if the specified keys are already handled by a - descriptor. - :raises TypeError: if this class has subclasses or if it is not the - :attr:`~AbstractDescriptor.root_class` of the given - descriptor. - - If the method raises, the caller must assume that registration was not - successful. - - .. note:: - - The intended audience for this method are developers of - :class:`AbstractDescriptor` subclasses, which are generally only - expected to live in the :mod:`aioxmpp` package. - - Thus, you should not expect this API to be stable. If you have a - use-case for using this function outside of :mod:`aioxmpp`, please - let me know through the usual issue reporting means. - """ - - if descriptor.root_class is not self or self.__subclasses__(): - raise TypeError( - "descriptors cannot be modified on classes with subclasses" - ) - - meta = type(self) - descriptor_info = meta._upcast_descriptor_map( - self.DESCRIPTOR_MAP, - "{}.{}".format(self.__module__, self.__qualname__), - ) - - # this would raise on conflict - meta._merge_descriptors( - descriptor_info, - [ - (key, (descriptor, "")) - for key in keys - ] - ) - - for key in keys: - self.DESCRIPTOR_MAP[key] = descriptor - - -class FormClass(DescriptorClass): - def from_xso(self, xso): - """ - Construct and return an instance from the given `xso`. - - .. note:: - - This is a static method (classmethod), even though sphinx does not - document it as such. - - :param xso: A :xep:`4` data form - :type xso: :class:`~.Data` - :raises ValueError: if the ``FORM_TYPE`` mismatches - :raises ValueError: if field types mismatch - :return: newly created instance of this class - - The fields from the given `xso` are matched against the fields on the - form. Any matching field loads its data from the `xso` field. Fields - which occur on the form template but not in the `xso` are skipped. - Fields which occur in the `xso` but not on the form template are also - skipped (but are re-emitted when the form is rendered as reply, see - :meth:`~.Form.render_reply`). - - If the form template has a ``FORM_TYPE`` attribute and the incoming - `xso` also has a ``FORM_TYPE`` field, a mismatch between the two values - leads to a :class:`ValueError`. - - The field types of matching fields are checked. If the field type on - the incoming XSO may not be upcast to the field type declared on the - form (see :meth:`~.FieldType.allow_upcast`), a :class:`ValueError` is - raised. - - If the :attr:`~.Data.type_` does not indicate an actual form (but - rather a cancellation request or tabular result), :class:`ValueError` - is raised. - """ - - my_form_type = getattr(self, "FORM_TYPE", None) - - f = self() - for field in xso.fields: - if field.var == "FORM_TYPE": - if (my_form_type is not None and - field.type_ == forms_xso.FieldType.HIDDEN and - field.values): - if my_form_type != field.values[0]: - raise ValueError( - "mismatching FORM_TYPE ({!r} != {!r})".format( - field.values[0], - my_form_type, - ) - ) - continue - if field.var is None: - continue - - key = fields.descriptor_ns, field.var - try: - descriptor = self.DESCRIPTOR_MAP[key] - except KeyError: - continue - - if (field.type_ is not None and not - field.type_.allow_upcast(descriptor.FIELD_TYPE)): - raise ValueError( - "mismatching type ({!r} != {!r}) on field var={!r}".format( - field.type_, - descriptor.FIELD_TYPE, - field.var, - ) - ) - - data = descriptor.__get__(f, self) - data.load(field) - - f._recv_xso = xso - - return f - - -class Form(metaclass=FormClass): - """ - A form template for :xep:`0004` Data Forms. - - Fields are declared using the different field descriptors available in this - module: - - .. autosummary:: - - TextSingle - TextMulti - TextPrivate - JIDSingle - JIDMulti - ListSingle - ListMulti - Boolean - - A form template can be instantiated by two different means: - - 1. the :meth:`from_xso` method can be called on a :class:`.xso.Data` - instance to fill in the template with the data from the XSO. - - 2. the constructor can be called. - - With the first method, labels, descriptions, options and values are taken - from the XSO. The descriptors declared on the form merely act as a - convenient way to access the fields in the XSO. - - If a field is missing from the XSO, its descriptor still works as if the - form had been constructed using its constructor. It will not be emitted - when re-serialising the form for a response using :meth:`render_reply`. - - If the XSO has more fields than the form template, these fields are - re-emitted when the form is serialised using :meth:`render_reply`. - - .. attribute:: LAYOUT - - A mixed list of descriptors and strings to determine form layout as - generated by :meth:`render_request`. The semantics are the following: - - * each :class:`str` is converted to a ``"fixed"`` field without ``var`` - attribute in the output. - * each :class:`AbstractField` descriptor is rendered to its - corresponding :class:`Field` XSO. - - The elements of :attr:`LAYOUT` are processed in-order. This attribute is - optional and can be set on either the :class:`Form` or a specific - instance. If it is absent, it is treated as if it were set to - ``list(self.DESCRIPTORS)``. - - .. automethod:: from_xso - - .. automethod:: render_reply - - .. automethod:: render_request - """ - - __slots__ = ("_descriptor_data", "_recv_xso") - - def __new__(cls, *args, **kwargs): - result = super().__new__(cls) - result._descriptor_data = {} - result._recv_xso = None - return result - - def __copy__(self): - result = type(self).__new__(type(self)) - result._descriptor_data.update(self._descriptor_data) - return result - - def __deepcopy__(self, memo): - result = type(self).__new__(type(self)) - result._descriptor_data = { - k: v.clone_for(self, memo=memo) - for k, v in self._descriptor_data.items() - } - return result - - def render_reply(self): - """ - Create a :class:`~.Data` object equal to the object from which the from - was created through :meth:`from_xso`, except that the values of the - fields are exchanged with the values set on the form. - - Fields which have no corresponding form descriptor are left untouched. - Fields which are accessible through form descriptors, but are not in - the original :class:`~.Data` are not included in the output. - - This method only works on forms created through :meth:`from_xso`. - - The resulting :class:`~.Data` instance has the :attr:`~.Data.type_` set - to :attr:`~.DataType.SUBMIT`. - """ - - data = copy.copy(self._recv_xso) - data.type_ = forms_xso.DataType.SUBMIT - data.fields = list(self._recv_xso.fields) - - for i, field_xso in enumerate(data.fields): - if field_xso.var is None: - continue - if field_xso.var == "FORM_TYPE": - continue - key = fields.descriptor_ns, field_xso.var - try: - descriptor = self.DESCRIPTOR_MAP[key] - except KeyError: - continue - - bound_field = descriptor.__get__(self, type(self)) - data.fields[i] = bound_field.render( - use_local_metadata=False - ) - - return data - - def render_request(self): - """ - Create a :class:`Data` object containing all fields known to the - :class:`Form`. If the :class:`Form` has a :attr:`LAYOUT` attribute, it - is used during generation. - """ - - data = forms_xso.Data(type_=forms_xso.DataType.FORM) - - try: - layout = self.LAYOUT - except AttributeError: - layout = list(self.DESCRIPTORS) - - my_form_type = getattr(self, "FORM_TYPE", None) - if my_form_type is not None: - field_xso = forms_xso.Field() - field_xso.var = "FORM_TYPE" - field_xso.type_ = forms_xso.FieldType.HIDDEN - field_xso.values[:] = [my_form_type] - data.fields.append(field_xso) - - for item in layout: - if isinstance(item, str): - field_xso = forms_xso.Field() - field_xso.type_ = forms_xso.FieldType.FIXED - field_xso.values[:] = [item] - else: - field_xso = item.__get__( - self, type(self) - ).render() - data.fields.append(field_xso) - - return data - - def _layout(self, usecase): - """ - Return an iterable of form members which are used to lay out the form. - - :param usecase: Configure the use case of the layout. This either - indicates transmitting the form to a peer as - *response*, as *initial form*, or as *error form*, or - *showing* the form to a local user. - - Each element in the iterable must be one of the following: - - * A string; gets converted to a ``"fixed"`` form field. - * A field XSO; gets used verbatimly - * A descriptor; gets converted to a field XSO - """ diff --git a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/xso.py b/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/xso.py deleted file mode 100644 index e90f733..0000000 --- a/tests/venv2/lib/python3.11/site-packages/aioxmpp/forms/xso.py +++ /dev/null @@ -1,643 +0,0 @@ -######################################################################## -# File name: xso.py -# This file is part of: aioxmpp -# -# LICENSE -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program. If not, see -# . -# -######################################################################## -import collections -import enum - -import aioxmpp -import aioxmpp.xso as xso - -from aioxmpp.utils import namespaces - -namespaces.xep0004_data = "jabber:x:data" - - -class Value(xso.XSO): - TAG = (namespaces.xep0004_data, "value") - - value = xso.Text(default="") - - -class ValueElement(xso.AbstractElementType): - def unpack(self, item): - return item.value - - def pack(self, value): - v = Value() - v.value = value - return v - - def get_xso_types(self): - return [Value] - - -class Option(xso.XSO): - TAG = (namespaces.xep0004_data, "option") - - label = xso.Attr( - tag="label", - default=None, - ) - - value = xso.ChildText( - (namespaces.xep0004_data, "value"), - default=None, - ) - - def validate(self): - if self.value is None: - raise ValueError("option is missing a value") - - -class OptionElement(xso.AbstractElementType): - def unpack(self, item): - return (item.value, item.label) - - def pack(self, value): - value, label = value - o = Option() - o.value = value - o.label = label - return o - - def get_xso_types(self): - return [Option] - - -class FieldType(enum.Enum): - """ - Enumeration containing the field types defined in :xep:`4`. - - .. seealso:: - - :attr:`Field.values` - for important information regarding typing, restrictions, validation - and constraints of values in that attribute. - - Each type has the following attributes and methods: - - .. automethod:: allow_upcast - - .. autoattribute:: has_options - - .. autoattribute:: is_multivalued - - Quotations in the following attribute descriptions are from said XEP. - - .. attribute:: BOOLEAN - - The ``"boolean"`` field: - - The field enables an entity to gather or provide an either-or choice - between two options. The default value is "false". - - The :attr:`Field.values` sequence should contain zero or one elements. - If it contains an element, it must be ``"0"``, ``"1"``, ``"false"``, or - ``"true"``, in accordance with the XML Schema documents. - - .. attribute:: FIXED - - The ``"fixed"`` field: - - The field is intended for data description (e.g., human-readable text - such as "section" headers) rather than data gathering or provision. - The child SHOULD NOT contain newlines (the ``\\n`` and - ``\\r`` characters); instead an application SHOULD generate multiple - fixed fields, each with one child. - - As such, the :attr:`Field.values` sequence should contain exactly one - element. :attr:`Field.desc`, :attr:`Field.label`, :attr:`Field.options` - and :attr:`Field.var` should be set to :data:`None` or empty containers. - - .. attribute:: HIDDEN - - The ``"hidden"`` field: - - The field is not shown to the form-submitting entity, but instead is - returned with the form. The form-submitting entity SHOULD NOT modify - the value of a hidden field, but MAY do so if such behavior is - defined for the "using protocol". - - This type is commonly used for the ``var="FORM_TYPE"`` field, as - specified in :xep:`68`. - - .. attribute:: JID_MULTI - - The ``"jid-multi"`` field: - - The field enables an entity to gather or provide multiple Jabber IDs. - Each provided JID SHOULD be unique (as determined by comparison that - includes application of the Nodeprep, Nameprep, and Resourceprep - profiles of Stringprep as specified in XMPP Core), and duplicate JIDs - MUST be ignored. - - As such, the :attr:`Field.values` sequence should contain zero or more - strings representing Jabber IDs. :attr:`Field.options` should be empty. - - .. attribute:: JID_SINGLE - - The ``"jid-single"`` field: - - The field enables an entity to gather or provide a single Jabber ID. - - As such, the :attr:`Field.values` sequence should contain zero or one - string representing a Jabber ID. :attr:`Field.options` should be empty. - - .. attribute:: LIST_MULTI - - The ``"list-multi"`` field: - - The field enables an entity to gather or provide one or more options - from among many. A form-submitting entity chooses one or more items - from among the options presented by the form-processing entity and - MUST NOT insert new options. The form-submitting entity MUST NOT - modify the order of items as received from the form-processing - entity, since the order of items MAY be significant. - - Thus, :attr:`Field.values` should contain a subset of the keys of the - :class:`Field.options` dictionary. - - .. attribute:: LIST_SINGLE - - The ``"list-single"`` field: - - The field enables an entity to gather or provide one option from - among many. A form-submitting entity chooses one item from among the - options presented by the form-processing entity and MUST NOT insert - new options. - - Thus, :attr:`Field.values` should contain a zero or one of the keys of - the :class:`Field.options` dictionary. - - .. attribute:: TEXT_MULTI - - The ``"text-multi"`` field: - - The field enables an entity to gather or provide multiple lines of - text. - - Each string in the :attr:`Field.values` attribute should be a single - line of text. Newlines are not allowed in data forms fields (due to the - ambiguity between ``\\r`` and ``\\n`` and combinations thereof), which - is why the text is split on the line endings. - - .. attribute:: TEXT_PRIVATE - - The ``"text-private"`` field: - - The field enables an entity to gather or provide a single line or - word of text, which shall be obscured in an interface (e.g., with - multiple instances of the asterisk character). - - The :attr:`Field.values` attribute should contain zero or one string - without any newlines. - - .. attribute:: TEXT_SINGLE - - The ``"text-single"`` field: - - The field enables an entity to gather or provide a single line or - word of text, which may be shown in an interface. This field type is - the default and MUST be assumed if a form-submitting entity receives - a field type it does not understand. - - The :attr:`Field.values` attribute should contain zero or one string - without any newlines. - - """ - - FIXED = "fixed" - HIDDEN = "hidden" - BOOLEAN = "boolean" - TEXT_SINGLE = "text-single" - TEXT_MULTI = "text-multi" - TEXT_PRIVATE = "text-private" - LIST_SINGLE = "list-single" - LIST_MULTI = "list-multi" - JID_SINGLE = "jid-single" - JID_MULTI = "jid-multi" - - @property - def has_options(self): - """ - true for the ``list-`` field types, false otherwise. - """ - return self.value.startswith("list-") - - @property - def is_multivalued(self): - """ - true for the ``-multi`` field types, false otherwise. - """ - return self.value.endswith("-multi") - - def allow_upcast(self, to): - """ - Return true if the field type may be upcast to the other field type - `to`. - - This relation specifies when it is safe to transfer data from this - field type to the given other field type `to`. - - This is the case if any of the following holds true: - - * `to` is equal to this type - * this type is :attr:`TEXT_SINGLE` and `to` is :attr:`TEXT_PRIVATE` - """ - - if self == to: - return True - if self == FieldType.TEXT_SINGLE and to == FieldType.TEXT_PRIVATE: - return True - return False - - -class Field(xso.XSO): - """ - Represent a single field in a Data Form. - - :param type_: Field type, must be one of the valid field types specified in - :xep:`4`. - :type type_: :class:`FieldType` - :param options: A mapping of values to labels defining the options in a - ``list-*`` field. - :type options: :class:`dict` mapping :class:`str` to :class:`str` - :param values: A sequence of values currently given for the field. Having - more than one value is only valid in ``*-multi`` fields. - :type values: :class:`list` of :class:`str` - :param desc: Description which can be shown in a tool-tip or similar, - without newlines. - :type desc: :class:`str` or :data:`None` - :param label: Human-readable label to be shown next to the field input - :type label: :class:`str` or :data:`None` - :param required: Flag to indicate that the field is required - :type required: :class:`bool` - :param var: "ID" identifying the field uniquely inside the form. Only - required for fields carrying a meaning (thus, not for - ``fixed``). - :type var: :class:`str` or :data:`None` - - The semantics of a :class:`Field` are different depending on where it - occurs: in a :class:`Data`, it is a form field to be filled in, in a - :class:`Item` it is a cell of a row and in a :class:`Reported` it - represents a column header. - - .. attribute:: required - - A boolean flag indicating whether the field is required. - - If true, the XML serialisation will contain the corresponding - ```` tag. - - .. attribute:: desc - - Single line of description for the field. This attribute represents the - ```` element from :xep:`4`. - - .. attribute:: values - - A sequence of strings representing the ```` elements of the - field, one string for each value. - - .. note:: - - Since the requirements on the sequence of strings in :attr:`values` - change depending on the :attr:`type_` attribute, validation and type - conversion on assignment is very lax. The attribute accepts all - sequences of strings, even if the field is for example a - :attr:`FieldType.BOOLEAN` field, which allows for at most one string - of a well-defined format (see the documentation there for the - details). - - This makes it easy to inadvertendly generate invalid forms, which is - why you should be using :class:`Form` subclasses when accessing forms - from within normal code and some other, generic mechanism taking care - of these details when showing forms in a UI framework to users. Note - that devising such a mechanism is out of scope for :mod:`aioxmpp`, as - every UI framework has different requirements. - - .. attribute:: options - - A dictionary mapping values to human-readable labels, representing the - ``